【学习笔记】点分治

一、概念

有一些题要求我们统计某些点对的数量,限制一般和点间的路径有关,\(O(n^2)\) 的时间复杂度无法承受。我们考虑首先选定一个根,此时路径分为两类:

  • 经过根
  • 不经过根

其中不经过根的可以在删掉根后在每个子树中进行统计,递归求解。于是只用处理经过根的情况。那么可以将这条路径拆成从一个点到根和从根到另一个点,方便地解决。于是只要递归层数合理即可获得一个相当优秀的时间复杂度。可以证明,只要每次选择的都是当前树的重心,递归层数就是 \(O(\log n)\) 的。

二、例题

1.[bzoj1468]Tree

考虑在当前树中处理出每个点到根的距离。将所有点的距离排序,有一个 \(l\) 指针和 \(r\) 指针分别指向序列的两端。可以发现当 \(l\)\(r\) 对应的距离之和满足要求,\([l+1,r-1]\) 中的点就都能满足要求。于是就累加答案,在移动 \(l\) 指针即可。否则就向左移动 \(r\) 指针。

可以发现这样同一个子树内会统计到不合法的答案,那就在每个子树内减掉即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=4e4+5,inf=0x3f3f3f3f;
int n,m,enm=1,hd[maxn];
struct{
	int v,w,nxt;
}e[maxn<<1];
il void addedge(int u,int v,int w){
	e[++enm].v=v;
	e[enm].w=w;
	e[enm].nxt=hd[u];
	hd[u]=enm;
}
vector<int> dv;
int sz[maxn],mxs[maxn],dis[maxn];
bool ban[maxn<<1];
il void dfs1(int u,int fa){
	sz[u]=1,mxs[u]=0;
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	mxs[u]=max(mxs[u],(int)dv.size()-sz[u]);
}
il void dfs2(int u,int fa){
	for(int i=hd[u],v,w;i;i=e[i].nxt){
		v=e[i].v,w=e[i].w;
		if(ban[i]||v==fa){
			continue;
		}
		dis[v]=dis[u]+w;
		dfs2(v,u);
	}
}
il int calc(){
	int res=0;
	int l=0,r=dv.size()-1;
	sort(dv.begin(),dv.end(),[](const int &x,const int &y){return dis[x]<dis[y];});
	while(l<r){
		if(dis[dv[l]]+dis[dv[r]]<=m){
			res+=r-l;
			l++;
		}
		else{
			r--;
		}
	}
	return res;
}
il void dfs3(int u,int fa){
	dv.pb(u);
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs3(v,u);
	}
}
il int solve(){
	if(dv.size()==1){
		return 0;
	}
	dfs1(dv[0],0);
	int mmxs=inf,rt=0;
	for(int u:dv){
		if(mmxs>mxs[u]){
			mmxs=mxs[u],rt=u;
		}
	}
	dis[rt]=0;
	dfs2(rt,0);
	int res=calc();
	for(int i=hd[rt];i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		dv.clear();
		dfs3(e[i].v,rt);
		res-=calc();
	}
	for(int i=hd[rt];i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		ban[i]=ban[i^1]=1;
		dv.clear();
		dfs3(e[i].v,0);
		res+=solve();
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		addedge(u,v,w);
		addedge(v,u,w);
	}
	cin>>m;
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	cout<<solve();
	return 0;
}
}
int main(){return asbt::main();}

2.聪聪可可

存储当前到根距离对 \(3\) 取模为 \(0,1,2\) 的点数,在每个子树上先统计答案,再将这个子树上的点加入。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define gcd __gcd
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=4e4+5,inf=0x3f3f3f3f;
int n,enm=1,hd[maxn];
struct{
	int v,w,nxt;
}e[maxn];
il void addedge(int u,int v,int w){
	e[++enm].v=v;
	e[enm].w=w;
	e[enm].nxt=hd[u];
	hd[u]=enm;
}
vector<int> dv;
int sz[maxn],mxs[maxn],dis[maxn];
bool ban[maxn];
il void dfs1(int u,int fa){
	sz[u]=1,mxs[u]=0;
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	mxs[u]=max(mxs[u],(int)dv.size()-sz[u]);
}
il void dfs2(int u,int fa){
	for(int i=hd[u],v,w;i;i=e[i].nxt){
		v=e[i].v,w=e[i].w;
		if(ban[i]||v==fa){
			continue;
		}
		dis[v]=dis[u]+w;
		dfs2(v,u);
	}
}
int ans1,ans2,nm[3];
il void dfs3(int u,int fa){
	ans1+=nm[((3-dis[u])%3+3)%3]<<1;
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs3(v,u);
	}
}
il void dfs4(int u,int fa){
	nm[dis[u]%3]++;
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs4(v,u);
	}
}
il void dfs5(int u,int fa){
	dv.pb(u);
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs5(v,u);
	}
}
il void solve(){
	dfs1(dv[0],0);
	int rt=0,mmxs=inf;
	for(int u:dv){
		if(mmxs>mxs[u]){
			mmxs=mxs[u],rt=u;
		}
	}
//	cout<<rt<<"\n";
	dis[rt]=0;
	dfs2(rt,0);
	nm[0]=1,nm[1]=nm[2]=0;
	ans1++;
	for(int i=hd[rt],v;i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		v=e[i].v;
		dfs3(v,rt);
		dfs4(v,rt);
	}
	for(int i=hd[rt];i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		ban[i]=ban[i^1]=1;
		dv.clear();
		dfs5(e[i].v,0);
		solve();
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		addedge(u,v,w);
		addedge(v,u,w);
	}
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	solve();
	ans2=n*n;
//	cout<<ans1<<"\n";
	cout<<ans1/gcd(ans1,ans2)<<"/"<<ans2/gcd(ans1,ans2);
	return 0;
}
}
signed main(){return asbt::main();}

3.[bzoj2599][IOI2011]Race

存储到根的每个距离的最小深度,类似例 \(2\) 的手法去求即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=4e5+5,maxm=1e6+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,enm=1,hd[maxn];
struct{
	int v,w,nxt;
}e[maxn];
il void addedge(int u,int v,int w){
	e[++enm].v=v;
	e[enm].w=w;
	e[enm].nxt=hd[u];
	hd[u]=enm;
}
vector<int> dv,vp;
int sz[maxn],mxs[maxn];
int dep[maxn],dis[maxn];
int mdis[maxm],ans=inf;
bool ban[maxn];
il void dfs1(int u,int fa){
	sz[u]=1,mxs[u]=0;
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	mxs[u]=max(mxs[u],(int)dv.size()-sz[u]);
}
il void dfs2(int u,int fa){
	for(int i=hd[u],v,w;i;i=e[i].nxt){
		v=e[i].v,w=e[i].w;
		if(ban[i]||v==fa){
			continue;
		}
		dep[v]=dep[u]+1;
		dis[v]=dis[u]+w;
		dfs2(v,u);
	}
}
template<typename T>il void dfs3(int u,int fa,T x){
	x(u);
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs3(v,u,x);
	}
}
il void solve(){
	dfs1(dv[0],0);
	int rt=0,mmxs=inf;
	for(int u:dv){
		if(mmxs>mxs[u]){
			mmxs=mxs[u],rt=u;
		}
	}
	dis[rt]=dep[rt]=0;
	dfs2(rt,0);
	vp.pb(0);
	mdis[0]=0;
	for(int i=hd[rt],v;i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		v=e[i].v;
		dfs3(v,rt,[](int x){if(dis[x]<=m) ans=min(ans,mdis[m-dis[x]]+dep[x]);});
		dfs3(v,rt,[](int x){if(dis[x]<=m) mdis[dis[x]]=min(mdis[dis[x]],dep[x]),vp.pb(dis[x]);});
	}
	for(int x:vp){
		mdis[x]=inf;
	}
	vp.clear();
	for(int i=hd[rt],v;i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		v=e[i].v;
		ban[i]=ban[i^1]=1;
		dv.clear();
		dfs3(v,0,[](int x){dv.pb(x);});
		solve(); 
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		u++,v++;
		addedge(u,v,w);
		addedge(v,u,w);
	}
	memset(mdis,0x3f,sizeof mdis);
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	solve();
	cout<<(ans>=inf?-1:ans);
	return 0;
}
}
signed main(){return asbt::main();}

4.采药人的路径

我们将所有 \(0\) 都记为 \(-1\),那么整条路径阴阳平衡用点分治就很简单了。考虑休息点的限制,那其实就是对于 \((u,v)\),从 \(u\)\(v\) 出发向上走的过程中前缀和存在 \(0\)。因为我们的 dfs 是从上往下的过程,前缀和序列需要区间加。但我们其实不用考虑前缀和序列的顺序,只用看其中有没有 \(0\) 就好了。于是可以用 FHQ-Treap 来维护。对于每个根链和,维护前缀和中有 \(0\) 的点的个数和没有 \(0\) 的点的个数即可。注意特判以根为端点的情况。时间复杂度 \(O(n\log^2n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
const int inf=0x3f3f3f3f;
int n,enm=1,hd[maxn];
int sz[maxn],mxs[maxn];
int nm1[maxn],nm2[maxn];
int sum[maxn],tot;
ll ans;
bool ban[maxn],zero[maxn];
vector<int> dv;
struct{
	int v,w,nxt;
}e[maxn];
il void addedge(int u,int v,int w){
	e[++enm].v=v;
	e[enm].w=w;
	e[enm].nxt=hd[u];
	hd[u]=enm;
}
il void dfs1(int u,int fa){
	sz[u]=1,mxs[u]=0;
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	mxs[u]=max(mxs[u],tot-sz[u]);
}
namespace fhq{
	int rt,tot,ls[maxn],rs[maxn];
	int zhi[maxn],jan[maxn],tag[maxn];
	int ljt[maxn],top,sz[maxn];
	il int New(int v){
		int p=top?ljt[top--]:++tot;
		ls[p]=rs[p]=0,sz[p]=1;
		zhi[p]=v,tag[p]=0;
		jan[p]=rand();
		return p;
	}
	il void pushup(int id){
		sz[id]=sz[ls[id]]+sz[rs[id]]+1;
	}
	il void pushdown(int id){
		if(tag[id]){
			tag[ls[id]]+=tag[id];
			tag[rs[id]]+=tag[id];
			zhi[ls[id]]+=tag[id];
			zhi[rs[id]]+=tag[id];
			tag[id]=0;
		}
	}
	il int merge(int p,int q){
		if(!p||!q){
			return p+q;
		}
		if(jan[p]<jan[q]){
			pushdown(p);
			rs[p]=merge(rs[p],q);
			pushup(p);
			return p;
		}
		pushdown(q);
		ls[q]=merge(p,ls[q]);
		pushup(q);
		return q;
	}
	il void split(int id,int &p,int &q,int v){
		if(!id){
			p=q=0;
			return ;
		}
		pushdown(id);
		if(zhi[id]<=v){
			p=id;
			split(rs[id],rs[p],q,v);
		}
		else{
			q=id;
			split(ls[id],p,ls[q],v);
		}
		pushup(id);
	}
	il void insert(int v){
		tag[rt]+=v,zhi[rt]+=v;
		int x,y;
		split(rt,x,y,v);
		rt=merge(merge(x,New(v)),y);
	}
	il void erase(int v){
		int x,y,z;
		split(rt,x,y,v-1);
		split(y,y,z,v);
		ljt[++top]=y;
		rt=merge(merge(x,ls[y]),merge(rs[y],z));
		tag[rt]-=v,zhi[rt]-=v;
	}
}
il void dfs2(int u,int fa){
	int x,y,z;
	fhq::split(fhq::rt,x,y,-1);
	fhq::split(y,y,z,0);
	zero[u]=y;
	if(!sum[u]&&fhq::sz[y]>1){
		ans++;
	}
	fhq::rt=fhq::merge(fhq::merge(x,y),z);
	if(zero[u]){
		ans+=nm1[tot-sum[u]];
	}
	else{
		ans+=nm2[tot-sum[u]];
	}
	for(int i=hd[u],v,w;i;i=e[i].nxt){
		v=e[i].v,w=e[i].w;
		if(ban[i]||v==fa){
			continue;
		}
		sum[v]=sum[u]+w;
		fhq::insert(w);
		dfs2(v,u);
		fhq::erase(w);
	}
}
template<typename T>il void dfs3(int u,int fa,T x){
	x(u);
	for(int i=hd[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(ban[i]||v==fa){
			continue;
		}
		dfs3(v,u,x);
	}
}
il void solve(){
	tot=dv.size();
	dfs1(dv[0],0);
	int tmp=inf,rt;
	for(int u:dv){
		if(tmp>mxs[u]){
			tmp=mxs[u];
			rt=u;
		}
	}
	for(int i=0;i<=tot<<1;i++){
		nm1[i]=nm2[i]=0;
	}
	for(int i=hd[rt],v,w;i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		v=e[i].v,w=e[i].w;
		sum[v]=w;
		fhq::insert(w);
		dfs2(v,rt);
		fhq::erase(w);
		dfs3(v,rt,[](int x){nm1[tot+sum[x]]++;if(zero[x]) nm2[tot+sum[x]]++;});
	}
	for(int i=hd[rt],v;i;i=e[i].nxt){
		if(ban[i]){
			continue;
		}
		v=e[i].v;
		ban[i]=ban[i^1]=1;
		dv.clear();
		dfs3(v,0,[](int x){dv.pb(x);});
		solve();
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		w=w?1:-1;
		addedge(u,v,w);
		addedge(v,u,w);
	}
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	solve();
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

5.「BJOI2017」树的难题

点分治后,对于和根相连的边颜色为 \(w\) 的路径,显然我们需要将之前的路径分成与根相连的边为 \(w\) 的路径和不为 \(w\) 的路径分别统计答案。那么我们将根连出的点按照边颜色排序,分别开两棵平衡树,每遇到新的颜色就将两颗平衡树合到一块即可。时间复杂度 \(O(n\log^2n)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5,inf=1e18;
int n,m,_l,_r,a[maxn],ans=-inf;
int sz[maxn],mxsz[maxn];
int col[maxn],dep[maxn],dis[maxn];
bool ban[maxn];
vector<int> dv;
vector<pii> e[maxn];
il void dfs1(int u,int fa,int tot){
	sz[u]=1,mxsz[u]=0;
	for(pii i:e[u]){
		int v=i.fir;
		if(v==fa||ban[v]){
			continue;
		}
		dfs1(v,u,tot);
		sz[u]+=sz[v];
		mxsz[u]=max(mxsz[u],sz[v]);
	}
	mxsz[u]=max(mxsz[u],tot-sz[u]);
}
il void dfs2(int u,int fa){
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa||ban[v]){
			continue;
		}
		dep[v]=dep[u]+1;
		col[v]=w,dis[v]=dis[u];
		if(w!=col[u]){
			dis[v]+=a[w];
		}
		dfs2(v,u);
	}
}
int RT[2],tot;
int ls[maxn],rs[maxn];
int shn[maxn],jan[maxn];
int zhi[maxn],mxz[maxn];
il int New(int x,int y){
	tot++;
	ls[tot]=rs[tot]=0;
	shn[tot]=x,jan[tot]=rand();
	zhi[tot]=mxz[tot]=y;
	return tot;
}
il void pushup(int id){
	mxz[id]=max({mxz[ls[id]],mxz[rs[id]],zhi[id]});
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jan[p]<jan[q]){
		rs[p]=merge(rs[p],q);
		pushup(p);
		return p;
	}
	ls[q]=merge(p,ls[q]);
	pushup(q);
	return q;
}
il void split(int id,int x,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	if(shn[id]<=x){
		p=id;
		split(rs[id],x,rs[p],q);
	}
	else{
		q=id;
		split(ls[id],x,p,ls[q]);
	}
	pushup(id);
}
il void insert(int &id,int x,int y){
	int p,q;
	split(id,x,p,q);
	id=merge(merge(p,New(x,y)),q);
}
il int query(int &id,int l,int r){
	int x,y,z;
	split(id,l-1,x,y);
	split(y,r,y,z);
	int res=mxz[y];
	id=merge(merge(x,y),z);
	return res;
}
il void dfs3(int &id){
	if(!id){
		return ;
	}
	dfs3(ls[id]),dfs3(rs[id]);
	mxz[id]=zhi[id];
	int x,y;
	split(RT[0],shn[id],x,y);
	RT[0]=merge(merge(x,id),y);
	id=0;
}
template<typename T>il void dfs4(int u,int fa,T x){
	x(u);
	for(pii i:e[u]){
		int v=i.fir;
		if(v==fa||ban[v]){
			continue;
		}
		dfs4(v,u,x);
	}
}
il void solve(){
	dfs1(dv[0],0,dv.size());
	int rt,tmp=inf;
	for(int u:dv){
		if(tmp>mxsz[u]){
			tmp=mxsz[u],rt=u;
		}
	}
	col[rt]=dep[rt]=dis[rt]=0;
	dfs2(rt,0);
	sort(e[rt].begin(),e[rt].end(),[](pii x,pii y){return x.sec<y.sec;});
	RT[0]=RT[1]=0;
	mxz[0]=-inf,tot=0;
	insert(RT[0],0,0);
	for(int i=0,v,w;i<e[rt].size();i++){
		v=e[rt][i].fir,w=e[rt][i].sec;
		if(i&&w!=e[rt][i-1].sec){
			dfs3(RT[1]);
		}
		if(ban[v]){
			continue;
		}
		dfs4(v,rt,[=](int x){ans=max({ans,dis[x]+query(RT[0],_l-dep[x],_r-dep[x]),dis[x]+query(RT[1],_l-dep[x],_r-dep[x])-a[w]});});
		dfs4(v,rt,[](int x){insert(RT[1],dep[x],dis[x]);});
	}
	ban[rt]=1;
	for(pii i:e[rt]){
		int v=i.fir,w=i.sec;
		if(ban[v]){
			continue;
		}
		dv.clear();
		dfs4(v,0,[](int x){dv.pb(x);});
		solve();
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>_l>>_r;
	for(int i=1;i<=m;i++){
		cin>>a[i];
	}
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		e[u].pb(mp(v,w));
		e[v].pb(mp(u,w));
	}
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	solve();
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

三、点分树

在点分治过程中,对当前子树的重心与删去后的每个子树的重心建立父子关系,这样可以建成一棵新树。对这棵树上的每一个点维护子树内的信息,就可以动态修改地进行点分治了。

1.[BZOJ3730]点分树 | 震波(模板)

在点分树的每一个点上用两棵平衡树维护距离与权值的信息(一棵维护到自己的距离,另一棵维护到点分树上父亲的距离)。修改和查询就只需考察根链就好了。

现学了一下替罪羊树,十分的快。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ull unsigned ll
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
namespace IO{
	const int bufsz=1<<20;
	char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
	#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
	il int read(){
		char ch=getchar();
		while(ch<'0'||ch>'9'){
			ch=getchar();
		}
		int x=0;
		while(ch>='0'&&ch<='9'){
			x=(x<<1)+(x<<3)+(ch^48);
			ch=getchar();
		}
		return x;
	}
	#undef getchar
}
using IO::read;
const int maxn=1e5+5,maxm=4e6+5;
const int inf=0x3f3f3f3f;
int n,m,a[maxn];
vector<int> e[maxn];
namespace LCA{
	int cnt,dep[maxn],dfn[maxn];
	int idx[maxn<<1],oula[maxn<<1];
	il void dfs(int u,int fa){
		dep[u]=dep[fa]+1;
		dfn[u]=++cnt;
		idx[cnt]=u;
		oula[cnt]=cnt;
		for(int v:e[u]){
			if(v==fa){
				continue;
			}
			dfs(v,u);
			oula[++cnt]=dfn[u];
		}
	}
	struct{
		int st[maxn<<1][22],Log[maxn<<1];
		il void build(){
			for(int i=2;i<=cnt;i++){
				Log[i]=Log[i>>1]+1;
			}
			for(int i=1;i<=cnt;i++){
				st[i][0]=oula[i];
			}
			for(int j=1;j<=Log[cnt];j++){
				for(int i=1;i+(1<<j)-1<=cnt;i++){
					st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
				}
			}
		}
		il int query(int l,int r){
			int tmp=Log[r-l+1];
			return min(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
		}
	}ST;
	il void init(){
		dfs(1,0);
		ST.build();
	}
	il int lca(int u,int v){
		if(dfn[u]>dfn[v]){
			swap(u,v);
		}
		return idx[ST.query(dfn[u],dfn[v])];
	}
	il int dis(int u,int v){
		return dep[u]+dep[v]-(dep[lca(u,v)]<<1);
	}
}
using LCA::dis;
int sz[maxn],mxsz[maxn];
int RT[maxn][2],fa[maxn];
bool ban[maxn];
vector<int> dv;
il void dfs1(int u,int fa,int tot){
	sz[u]=1,mxsz[u]=0;
	for(int v:e[u]){
		if(ban[v]||v==fa){
			continue;
		}
		dfs1(v,u,tot);
		sz[u]+=sz[v];
		mxsz[u]=max(mxsz[u],sz[v]);
	}
	mxsz[u]=max(mxsz[u],tot-sz[u]);
}
template<typename T>il void dfs2(int u,int fa,T x){
	x(u);
	for(int v:e[u]){
		if(v==fa||ban[v]){
			continue;
		}
		dfs2(v,u,x);
	}
}
namespace gtree{
	int tot,ls[maxm],rs[maxm];
	int pos[maxm],sz[maxm];
	int zhi[maxm],sum[maxm];
	int mnl[maxm],mxr[maxm];
	int hp[maxm],cnt;
	il void pushup(int id){
		sz[id]=sz[ls[id]]+sz[rs[id]]+1;
		sum[id]=sum[ls[id]]+sum[rs[id]]+zhi[id];
		mnl[id]=ls[id]?mnl[ls[id]]:pos[id];
		mxr[id]=rs[id]?mxr[rs[id]]:pos[id];
	}
	il void flatten(int id){
		if(!id){
			return ;
		}
		flatten(ls[id]);
		hp[++cnt]=id;
		flatten(rs[id]);
	}
	il void build(int &id,int l,int r){
		if(l>r){
			id=0;
			return ;
		}
		int mid=(l+r)>>1;
		id=hp[mid];
		build(ls[id],l,mid-1);
		build(rs[id],mid+1,r);
		pushup(id);
	}
	il void rebuild(int &id){
		if(max(sz[ls[id]],sz[rs[id]])>=sz[id]*0.75){
			cnt=0;
			flatten(id);
			build(id,1,cnt);
		}
	}
	il void insert(int &id,int x,int y){
		if(!id){
			id=++tot;
			sz[id]=1;
			mnl[id]=mxr[id]=pos[id]=x;
			zhi[id]=sum[id]=y;
			return ;
		}
		if(pos[id]==x){
			zhi[id]+=y,sum[id]+=y;
			return ;
		}
		if(x<pos[id]){
			insert(ls[id],x,y);
		}
		else{
			insert(rs[id],x,y);
		}
		pushup(id);
		rebuild(id);
	}
	il int query(int id,int l,int r){
		if(!id){
			return 0;
		}
		if(mnl[id]>=l&&mxr[id]<=r){
			return sum[id];
		}
		int res=l<=pos[id]&&r>=pos[id]?zhi[id]:0;
		if(l<pos[id]){
			res+=query(ls[id],l,r);
		}
		if(r>pos[id]){
			res+=query(rs[id],l,r);
		}
		return res;
	}
}
il int build(){
	dfs1(dv[0],0,dv.size());
	int rt,tmp=inf;
	for(int u:dv){
		if(tmp>mxsz[u]){
			tmp=mxsz[u],rt=u;
		}
	}
	for(int u:dv){
		gtree::insert(RT[rt][0],dis(u,rt),a[u]);
	}
	ban[rt]=1;
	for(int v:e[rt]){
		if(ban[v]){
			continue;
		}
		dv.clear();
		dfs2(v,0,[](int x){dv.pb(x);});
		int nrt=build();
		fa[nrt]=rt;
		dfs2(v,0,[=](int x){gtree::insert(RT[nrt][1],dis(x,rt),a[x]);});
	}
	ban[rt]=0;
	return rt;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		e[u].pb(v),e[v].pb(u); 
	}
	LCA::init();
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	build();
	ll ans=0;
	while(m--){
		int opt,u,v;
		opt=read(),u=read(),v=read();
		u^=ans,v^=ans;
		if(opt){
			int p=u;
			while(p){
				gtree::insert(RT[p][0],dis(u,p),v-a[u]);
				if(fa[p]){
					gtree::insert(RT[p][1],dis(u,fa[p]),v-a[u]);
				}
				p=fa[p];
			}
			a[u]=v;
		}
		else{
			ans=0;
			int p=u;
			while(p){
				ans+=gtree::query(RT[p][0],0,v-dis(u,p));
				if(fa[p]){
					ans-=gtree::query(RT[p][1],0,v-dis(u,fa[p]));
				}
				p=fa[p];
			}
			printf("%lld\n",ans);
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

2.「ZJOI2015」幻想乡战略游戏

考虑一个暴力的贪心:首先将决策点设为 \(1\),然后每次选择一个比自己更优的儿子走过去,如果没有比自己更优的儿子那决策点就是自己。

\(u\) 为根,那么 \(u\) 的儿子 \(v\)\(u\) 优当且仅当:

\[(sum_u-sum_v)\times\operatorname{dis}(u,v)<sum_v\times\operatorname{dis}(u,v)\\ \]

也就是 \(2\times sum_v>sum_u\)。其中 \(sum_u\) 表示 \(u\) 的子树内的军队数。显然这样的儿子最多只有一个。而如果 \(u\) 不是根,\(u\) 能比 \(fa_u\) 优则 \(fa_u\) 一定没有 \(u\) 优,所以也就只用考虑儿子,不用考虑父亲。

这个做法的时间复杂度显然和树的深度有关。因此我们建点分树,将树的深度控制在 \(O(\log n)\) 级别。

此时的思路还是一样的:从根开始,找到自己在原树上的更优的儿子,然后在点分数上走到包含那个儿子的子树。没有更优的儿子说明自己就是最优决策点。

在点分数上对每个点维护子树军队数,子树军队数乘距离,以及子树军队数到父亲的距离。这样对于每个决策点,计算答案就是 \(O(\log n)\) 的,从而总时间复杂度为 \(O(20n\log^2n)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,inf=1e18;
int n,m;
vector<pii> e[maxn],E[maxn];
namespace LCA{
	int dfn[maxn],cnt,dep[maxn];
	int oula[maxn<<1],idx[maxn<<1];
	il void dfs(int u,int fa){
		dfn[u]=++cnt;
		idx[cnt]=u;
		oula[cnt]=cnt;
		for(pii i:e[u]){
			int v=i.fir,w=i.sec;
			if(v==fa){
				continue;
			}
			dep[v]=dep[u]+w;
			dfs(v,u);
			oula[++cnt]=dfn[u];
		}
	}
	struct{
		int Log[maxn<<1],st[maxn<<1][22];
		il void build(){
			for(int i=2;i<=cnt;i++){
				Log[i]=Log[i>>1]+1;
			}
			for(int i=1;i<=cnt;i++){
				st[i][0]=oula[i];
			}
			for(int j=1;j<=Log[cnt];j++){
				for(int i=1;i+(1<<j)-1<=cnt;i++){
					st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
				}
			}
		}
		il int query(int l,int r){
			int p=Log[r-l+1];
			return min(st[l][p],st[r-(1<<p)+1][p]);
		}
	}ST;
	il void init(){
		dfs(1,0);
		ST.build();
	}
	il int lca(int u,int v){
		if(dfn[u]>dfn[v]){
			swap(u,v);
		}
		return idx[ST.query(dfn[u],dfn[v])];
	}
	il int dis(int u,int v){
		return dep[u]+dep[v]-dep[lca(u,v)]*2;
	}
}
using LCA::dis;
int sz[maxn],mxs[maxn],fa[maxn];
bool ban[maxn];
vector<int> dv;
il void dfs1(int u,int fa,int tot){
	sz[u]=1,mxs[u]=0;
	for(pii i:e[u]){
		int v=i.fir;
		if(v==fa||ban[v]){
			continue;
		}
		dfs1(v,u,tot);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	mxs[u]=max(mxs[u],tot-sz[u]);
}
il void dfs2(int u,int fa){
	dv.pb(u);
	for(pii i:e[u]){
		int v=i.fir;
		if(v==fa||ban[v]){
			continue;
		}
		dfs2(v,u);
	}
}
il int build(){
	dfs1(dv[0],0,dv.size());
	int rt,tmp=inf;
	for(int u:dv){
		if(tmp>mxs[u]){
			tmp=mxs[u],rt=u;
		}
	}
	ban[rt]=1;
	for(pii i:e[rt]){
		int v=i.fir;
		if(ban[v]){
			continue;
		}
		dv.clear();
		dfs2(v,0);
		int tmp=build();
		E[rt].pb(mp(v,tmp));
		fa[tmp]=rt;
	}
	return rt;
}
int sum[maxn],sumd[maxn],sumf[maxn];
il void upd(int u,int x){
	sum[u]+=x;
	for(int i=u;fa[i];i=fa[i]){
		sum[fa[i]]+=x;
		int tmp=dis(u,fa[i]);
		sumd[fa[i]]+=x*tmp;
		sumf[i]+=x*tmp;
	}
}
il int calc(int u){
	int res=sumd[u];
	for(int i=u;fa[i];i=fa[i]){
		res+=sumd[fa[i]]-sumf[i];
		res+=(sum[fa[i]]-sum[i])*dis(u,fa[i]);
	}
	return res;
}
il int query(int u){
	int res=calc(u);
	for(pii i:E[u]){
		if(calc(i.fir)<res){
			return query(i.sec);
		}
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		e[u].pb(mp(v,w));
		e[v].pb(mp(u,w));
	}
	LCA::init();
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=n;j++){
//			cout<<dis(i,j)<<" ";
//		}
//		puts("");
//	}
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	int rt=build();
	while(m--){
		int u,x;
		cin>>u>>x;
		upd(u,x);
		cout<<query(rt)<<"\n";
	}
	return 0;
}
}
signed main(){return asbt::main();}

3.「HNOI2015」开店

是道简单题,建出点分树后预处理分治子树对自己和父亲的贡献,查询暴力爬树即可。但是用数据结构会因为常数过大喜提 60 分,考虑到是个静态问题,将子树信息排序后存成 vector,查询二分即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define lwrb lower_bound
#define uprb upper_bound
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1.5e5+5;
int n,m,A,a[maxn];
vector<pii> e[maxn];
namespace LCA{
	int cnt,dfn[maxn],dep[maxn];
	int oula[maxn<<1],idx[maxn<<1];
	il void dfs(int u,int fa){
		dfn[u]=++cnt;
		oula[cnt]=cnt;
		idx[cnt]=u;
		for(pii i:e[u]){
			int v=i.fir,w=i.sec;
			if(v==fa){
				continue;
			}
			dep[v]=dep[u]+w;
			dfs(v,u);
			oula[++cnt]=dfn[u];
		}
	}
	struct{
		int Log[maxn<<1],st[maxn<<1][22];
		il void build(){
			for(int i=2;i<=cnt;i++){
				Log[i]=Log[i>>1]+1;
			}
			for(int i=1;i<=cnt;i++){
				st[i][0]=oula[i];
			}
			for(int j=1;j<=Log[cnt];j++){
				for(int i=1;i+(1<<j)-1<=cnt;i++){
					st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
				}
			}
		}
		il int query(int l,int r){
			int p=Log[r-l+1];
			return min(st[l][p],st[r-(1<<p)+1][p]);
		}
	}ST;
	il void init(){
		dfs(1,0),ST.build();
	}
	il int lca(int u,int v){
		if(dfn[u]>dfn[v]){
			swap(u,v);
		}
		return idx[ST.query(dfn[u],dfn[v])];
	}
	il int dis(int u,int v){
		return dep[u]+dep[v]-dep[lca(u,v)]*2;
	}
}
using LCA::dis;
int tot,fa[maxn],sz[maxn],mxs[maxn];
bool ban[maxn];
pii hp[maxn];
vector<int> dv,nia[maxn][2],jul[maxn][2];
il void dfs1(int u,int fa,int tot){
	sz[u]=1,mxs[u]=0;
	for(pii i:e[u]){
		int v=i.fir;
		if(v==fa||ban[v]){
			continue;
		}
		dfs1(v,u,tot);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	mxs[u]=max(mxs[u],tot-sz[u]);
}
template<typename T>il void dfs2(int u,int fa,T x){
	x(u);
	for(pii i:e[u]){
		int v=i.fir;
		if(v==fa||ban[v]){
			continue;
		}
		dfs2(v,u,x);
	}
}
il void upd(int u,int d){
//	cout<<tot<<"\n";
	sort(hp+1,hp+tot+1);
	nia[u][d].pb(-1),jul[u][d].pb(0);
	for(int i=1;i<=tot;i++){
		nia[u][d].pb(hp[i].fir),jul[u][d].pb(hp[i].sec);
	}
	for(int i=1;i<=tot;i++){
		jul[u][d][i]+=jul[u][d][i-1];
	}
}
il int build(){
	dfs1(dv[0],0,dv.size());
	int u=0,mns=1e9;
	for(int v:dv){
		if(mns>mxs[v]){
			mns=mxs[v],u=v;
		}
	}
//	for(int v:dv){
//		cout<<v<<" ";
//	}
//	cout<<": "<<u<<"\n";
	tot=0,dfs2(u,0,[=](int x){hp[++tot]=mp(a[x],dis(u,x));});
	upd(u,0);
	ban[u]=1;
	for(pii i:e[u]){
		int v=i.fir;
		if(ban[v]){
			continue;
		}
		dv.clear();
		dfs2(v,0,[](int x){dv.pb(x);});
		int p=build();
		fa[p]=u,tot=0;
		dfs2(p,0,[=](int x){hp[++tot]=mp(a[x],dis(u,x));});
		upd(p,1);
	}
	ban[u]=0;
	return u;
}
il pii query(int u,int d,int L,int R){
//	cout<<x.size()<<"\n";
	int l=lwrb(nia[u][d].begin(),nia[u][d].end(),L)-nia[u][d].begin()-1;
	int r=uprb(nia[u][d].begin(),nia[u][d].end(),R)-nia[u][d].begin()-1;
//	cout<<l<<" "<<r<<"\n";
	return mp(jul[u][d][r]-jul[u][d][l],r-l);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>A;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		e[u].pb(mp(v,w));
		e[v].pb(mp(u,w));
	}
	LCA::init();
	for(int i=1;i<=n;i++){
		dv.pb(i);
	}
	build();
//	for(int i=1;i<=n;i++){
//		cout<<fa[i]<<" ";
//	}
//	puts("");
//	for(int i=1;i<=n;i++){
//		cout<<nia[i][0].size()<<" "<<jul[i][0].size()<<" ";
//		cout<<nia[i][1].size()<<" "<<jul[i][1].size()<<"\n";
//	}
	int ans=0;
	while(m--){
		int u,l,r;
		cin>>u>>l>>r;
		l=(l+ans)%A,r=(r+ans)%A;
		if(l>r){
			swap(l,r);
		}
		ans=query(u,0,l,r).fir;
//		puts("666");
		for(int i=u;fa[i];i=fa[i]){
			pii res=query(i,1,l,r);
			ans-=res.fir+res.sec*dis(u,fa[i]);
			res=query(fa[i],0,l,r);
			ans+=res.fir+res.sec*dis(u,fa[i]);
		}
		cout<<ans<<"\n";
	}
	return 0;
}
}
signed main(){return asbt::main();}
posted @ 2025-02-23 14:41  zhangxy__hp  阅读(41)  评论(0)    收藏  举报