树分治

点分树

关于模拟赛 \(T2\) 考点分树这件事。

点分治

点分树被称为动态点分治,所以下面先介绍点分治。

P3806 【模板】点分治 1

点分治板子。考虑一个树上的路径,如果已知所有 \(dis,\) 可以按是否经过根划分为:

  1. 经过根:\(dis[u]+dis[v],\) 这个方便用桶处理。
  2. 没有经过根:经过了根的子树的根,分治处理根的子树。这也就是点分治。

所以只需要慢慢分治就好了。但是你会发现这样做的复杂度是 \(\mathcal{O(n^2)}\) 的,不可接受。所以考虑优化。

注意到每次的递归数量取决于子树大小,为了让每个子树都很小,可以考虑将重心作为子树的根。

每一次分治的时候分治子树重心就好了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10,inf=1e7,maxn=1e7+10;
int edg,fir[N],nxt[N],to[N],w[N],que[N],sz[N],maxi[N],dis[N],ans[N];
int n,m,rt,top,cnt,sum,stk[N],dd[N];
bitset<maxn>vis,tf;
inline void add(int x,int y,int z){	nxt[++edg]=fir[x],fir[x]=edg,to[edg]=y,w[edg]=z; }
void dfs1(int u,int f){
	sz[u]=1,maxi[u]=0;
	for(int t=fir[u];t;t=nxt[t]){
		int v=to[t];
		if(v^f&&!vis[v])dfs1(v,u),sz[u]+=sz[v],maxi[u]=max(maxi[u],sz[v]);
	}
	maxi[u]=max(maxi[u],sum-sz[u]);
	rt=(maxi[u]<maxi[rt]?u:rt);
}
void dfs2(int u,int f){
	dd[++cnt]=dis[u];
	for(int t=fir[u];t;t=nxt[t]){
		int v=to[t];
		if(v^f&&!vis[v])dis[v]=dis[u]+w[t],dfs2(v,u);
	}
	return;
}
void dfz(int u,int f){
	vis[u]=true,stk[++top]=0,tf[0]=true;
	for(int t=fir[u];t;t=nxt[t]){
		int v=to[t];
		if(v^f&&!vis[v]){
			dis[v]=w[t];
			dfs2(v,u);
			dis[v]=w[t],dfs2(v,u);
			for(int k=1;k<=cnt;++k)for(int i=1;i<=m;++i)if(que[i]>=dd[k])ans[i]|=tf[que[i]-dd[k]];
			for(int k=1;k<=cnt;++k)if(dd[k]<inf)stk[++top]=dd[k],tf[dd[k]]=true;
			cnt=0;
		}
	}
	while(top)tf[stk[top--]]=0;
	for(int t=fir[u];t;t=nxt[t]){
		int v=to[t];
		if(!vis[v]&&v^f)sum=sz[v],rt=0,maxi[rt]=inf,dfs1(v,u),dfz(rt,u);
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1,x,y,z;i<n;++i)cin>>x>>y>>z,add(x,y,z),add(y,x,z);
	for(int i=1;i<=m;++i)cin>>que[i];
	maxi[rt]=inf,sum=n,rt=0,dfs1(1,-1),dfz(rt,-1);
	for(int i=1;i<=m;++i)if(ans[i])cout<<"AYE\n";else cout<<"NAY\n";
	return 0;
}

P4178 Tree

和上一道题大致相同,注意到复杂度瓶颈在于统计 \(tf.\) 考虑到 \(tf\) 的计算区间是连续的 ,使用一个树状数组维护之。

posted @ 2024-10-11 10:13  ChihiroFujisaki  阅读(36)  评论(0)    收藏  举报