bzoj5518: [Zjoi2019]语言(树剖+扫描线)

传送门
这题完全可以考虑暴力艹标算。
不过还是 s t O   O ( n log ⁡ n ) stO\ O(n\log n) stO O(nlogn)的标算。
假设当前处理的路径是 ( u , v ) (u,v) (u,v),对应的点的编号为 ( a 1 , a 2 , . . . , a k ) (a_1,a_2,...,a_k) (a1,a2,...,ak),那么可以想成把 k 2 k^2 k2个小矩形 ( a i , a j ) (a_i,a_j) (ai,aj)全部覆盖。
于是最后只用询问被覆盖的矩形数,即矩形面积并,扫描线即可。

这样总复杂度是 O ( n ∗ log ⁡ n ∑ i = 1 m p a t h _ l e n g t h i 2 ) O(n*\log n\sum_{i=1}^mpath\_length_i^2) O(nlogni=1mpath_lengthi2)
然后如果我们能使路径上点的编号变成连续的 s s s段,那么每次只需要拆成 s 2 s^2 s2个矩形。
于是不难想到用重链剖分将复杂度优化到 O ( n log ⁡ 3 n ) O(n\log^3n) O(nlog3n).
然后你就 T L E TLE TLE成了 60 p t s 60pts 60pts因为吉司机把你树剖的两个 l o g log log给卡满啦!

于是我们考虑如何卡常过题。

首先把线段树换成 z k w zkw zkw线段树来卡常。
然后每次我们只询问全局被覆盖的点数,这样统计出来是一个无序点对。
A n s = ( 总 点 对 数 + 未 被 覆 盖 的 ( i , i ) 的 数 量 − n ) / 2 Ans=(总点对数+未被覆盖的(i,i)的数量-n)/2 Ans=(+(i,i)n)/2
那个未被覆盖的 ( i , i ) (i,i) (i,i)可以树上差分一波。
然后就能过啦!
代码:

#include<bits/stdc++.h>
#define ri register int
#define fi first
#define se second
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
typedef pair<int,int> pii;
const int N=1e5+5;
typedef long long ll;
int n,m,siz[N],hson[N],dep[N],top[N],fa[N],num[N],tot=0,cnt[N];
vector<int>e[N];
void dfs1(int p){
	siz[p]=1; 
	for(ri i=0,v;i<e[p].size();++i){
		if((v=e[p][i])==fa[p])continue;
		fa[v]=p,dep[v]=dep[p]+1,dfs1(v),siz[p]+=siz[v];
		if(siz[v]>siz[hson[p]])hson[p]=v;
	}
}
void dfs2(int p,int tp){
	num[p]=++tot,top[p]=tp;
	if(!hson[p])return;
	dfs2(hson[p],tp);
	for(ri i=0,v;i<e[p].size();++i)if((v=e[p][i])!=fa[p]&&v!=hson[p])dfs2(v,v);
}
vector<pii>ad[N],dc[N];
inline void insert(int x1,int y1,int x2,int y2){
	ad[x1].push_back(pii(y1,y2));
	dc[x2+1].push_back(pii(y1,y2));
}
inline void modify(int x,int y){
	static pii t[N];
	int tp=0;
	++cnt[x],++cnt[y];
	while(top[x]^top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		t[++tp]=pii(num[top[x]],num[x]);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])swap(x,y);
	--cnt[y],--cnt[fa[y]];
	t[++tp]=pii(num[y],num[x]);
	for(ri i=1;i<=tp;++i)for(ri j=1;j<=tp;++j)insert(t[i].fi,t[j].fi,t[i].se,t[j].se);
}
int ql,qr;
namespace sgt{
	#define lc (p<<1)
	#define rc (p<<1|1)
	int len[N<<3],cov[N<<3],ss[N<<3],m;
	inline void build(){
		for(m=1;m<=n;m<<=1);
		for(ri i=1;i<=n;++i)len[m+i]=1;
		for(ri p=m-1;p;--p)len[p]=len[lc]+len[rc];
	}
	inline void pushup(int p){ss[p]=cov[p]?len[p]:ss[lc]+ss[rc];}
	inline void update(int v){
		for(ql+=m-1,qr+=m+1;ql^qr^1;ql>>=1,qr>>=1){
			if(ql&1^1)cov[ql^1]+=v,pushup(ql^1);
			if(qr&1)cov[qr^1]+=v,pushup(qr^1);
			pushup(ql),pushup(qr);
		}
		pushup(qr),qr>>=1;
		while(ql)pushup(ql),ql>>=1;
	}
	#undef lc
	#undef rc
	#undef mid
}
ll ans=0;
void dfs(int p){
	for(ri i=0,v;i<e[p].size();++i)if((v=e[p][i])^fa[p])dfs(v),cnt[p]+=cnt[v];
	if(!cnt[p])++ans;
}
int main(){
    n=read(),m=read();
	for(ri i=1,u,v;i<n;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
	dfs1(n),dfs2(n,n);
	for(ri i=1;i<=m;++i)modify(read(),read());
	sgt::build();
	dfs(n);
	for(ri i=1;i<=n;++i){
		for(ri j=0,up=ad[i].size();j<up;++j)ql=ad[i][j].fi,qr=ad[i][j].se,sgt::update(1);
		for(ri j=0,up=dc[i].size();j<up;++j)ql=dc[i][j].fi,qr=dc[i][j].se,sgt::update(-1);
		ans+=sgt::ss[1];
	}
	cout<<(ans-n)/2;
	exit(0);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值