点分治
点分治
前置知识:树的重心
树形dp即可。
对于一棵树,我们可以把其中的路径分为两种,一种是过根节点的,一种是不过根节点的。
过根节点的路径我们很容易处理:
我们枚举它的子树,把已经出现过的路径长度维护起来。
当枚举到下一个子树的时候,与上面维护的路径一同计算即可。
对于不过根节点的路径,我们怎么处理呢?
这个时候就用到点分治了。
当我们计算完了过根节点的时候,把根节点删去,分成几个子树分别计算即可。
但是有一个要注意的地方,一颗子树的根节点定为哪个好?
定为子树的重心最好。
这里由于知识浅薄,不做解释,但是知道的一点是这样的话可以避免被链型数据卡掉。
复杂度为\(O(nm \log n)\),\(m\)为询问个数。
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e4+10,M=100+10,K=1e7+10;
int n,m,q[M];
int head[N],edge[2*N],ver[2*N],nxt[2*N],tot,sum,rt;
int size[N],maxp[N],rem[N],vis[N],dis[N],have[K],ans[M];
void addedge(int x,int y,int z) {
ver[++tot]=y;
edge[tot]=z;
nxt[tot]=head[x];
head[x]=tot;
}
void getrt(int u,int fa) {
size[u]=1;
maxp[u]=0;
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
if(v==fa||vis[v]) continue;
getrt(v,u);
size[u]+=size[v];
maxp[u]=max(maxp[u],size[v]);
}
maxp[u]=max(maxp[u],sum-size[u]);
if(maxp[u]<maxp[rt]) rt=u;
}
void getdis(int u,int fa) {
rem[++rem[0]]=dis[u];
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i],z=edge[i];
if(v==fa||vis[v]) continue;
dis[v]=dis[u]+z;
getdis(v,u);
}
}
void calc(int u) {
int p=0,del[N];
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i],z=edge[i];
if(vis[v]) continue;
rem[0]=0; dis[v]=z;
getdis(v,u);
for(int j=1; j<=rem[0]; j++)
for(int k=1; k<=m; k++)
if(q[k]>=rem[j])
ans[k]|=have[q[k]-rem[j]];
for(int j=1; j<=rem[0]; j++) {
if(rem[j]>=K) continue;
del[++p]=rem[j];
have[rem[j]]=1;
}
}
for(int i=1; i<=p; i++)
have[del[i]]=0;
}
void dfz(int u) {
vis[u]=have[0]=1;
calc(u);
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
if(vis[v]) continue;
sum=size[v];
maxp[rt=0]=sum;
getrt(v,0); dfz(rt);
}
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1,u,v,w; i<n; i++) {
scanf("%d %d %d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
for(int i=1; i<=m; i++)
scanf("%d",&q[i]);
maxp[rt]=sum=n;
getrt(1,0);
dfz(rt);
for(int i=1; i<=m; i++) {
if(ans[i]) printf("AYE\n");
else printf("NAY\n");
}
return 0;
}
P4178 Tree
当我们点分治时,得到了以重心为起点的所有链,那么我们怎么把这些路径合并呢?
要求距离小于等于\(k\),发现可以时用树状数组代替桶维护链合并。
当我们处理一条链长度为 \(m\) 时,用树状数组求出之前长度小于等于\(k-m\)的链的数量。最后再把这条链加入树状数组。注意不要计算同一条子树内的两条链了。
P3714 [BJOI2017]树的难题
当我们点分治时,得到了以重心为起点的所有链,那么我们怎么把这些路径合并呢?
很明显,链分为两种,一种是和重心颜色相同的,另一种是与重心颜色不同的。分别开线段树维护最大值。
当我们处理一条链长度为 \(m\) 时,链的权值是 \(k\) ,那么我们就在两颗线段树中找路径长为 \([l-m,r-m]\) 中的最大值然后与这条链合并。
当处理完一颗子树时,最后再把链加入线段树。
P5351 Ruri Loves Maschera
合并链时,把所有的链按照权值排序,从小到大在枚举,在树状数组中查询对前面链的贡献,因为这样保证了最大值为现在的链的权值。然后把这个链加入树状数组。
注意要减去子树内的贡献。
因为\(x\)到\(y\)的路径和\(y\)到\(x\)的路径视为两条路径,所以最后答案乘以2。
CF293E Close Vertices
合并链时,把所有链按照权值从小到大排序,建立指针 \(i\) 指向最大链。
此时要做得就是找到权值和边数同时满足限制的链。
指针 \(j\) 指向最小的链,使\(j\),\(i\) 合并的链满足权值限制。
把 \(1-j\) 所有链加入以边数为下标的树状数组。统计答案。
接着 \(i'\) 指向上一条链,找到新的 \(j'\),把\(j-j'\)经过的链加入树状数组。以此类推。
由于链已经排序,所以具有单调性。
注意要减去多的贡献。
如果用边数排序,也可以使用平衡树。
CF1101D GCD Counting
把重心的质因数全部处理出来,对于每条链因为gcd都是质因数的倍数,最多的质因数也就7,8个而已,这样就建立桶来匹配了。
CF321C Ciel the Commander
若是在重心放了A,那么就相当于可以删除了重心了,分成子树考虑,同时后面就不能再用A了。下面的子树就在重心放B,同理,这就是点分治的模板。