【树】点分治和点分树
点分治
学习资料:OI WiKi树分治
时间复杂度 \(\mathcal{O(nlogn)}\),
这里是指 \(solve(\;)\)函数需要 \(\mathcal{O(n)}\),再加上分治的 \(logn\)。所以前提是 \(solve()\)函数的复杂度不要太糟糕。
作用
处理树的大量路径信息。
做法(模板)
OI WiKi: 我们先随意选择一个节点作为根节点 \(rt\) ,所有完全位于其子树中的路径可以分为两种,一种是经过当前根节点的路径,一种是不经过当前根节点的路径。对于经过当前根节点的路径,又可以分为两种,一种是以根节点为一个端点的路径,另一种是两个端点都不为根节点的路径。而后者又可以由两条属于前者链合并得到。所以,对于枚举的根节点 \(rt\) ,我们先计算在其子树中且经过该节点的路径对答案的贡献,再递归其子树对不经过该节点的路径进行求解。
\(code:\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=2e5+5;
void read(int &x)
{
char c;
while(!isdigit(c=getchar()));x=c-'0';
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
}
vector<int>p[maxn];
int root,Tsiz,maxsiz;
int siz[maxn],sonsiz[maxn];
/*
root是每回的树根,重心求后做根
Tsiz是当前树的大小,即siz[u](假设u是当前树的根节点)
maxsiz初始化要inf,之后再求重心
sonsiz是重儿子的大小
*/
bool vis[maxn];//vis是用来标记做过根的点(扫过的点)
void getG(int u,int f)//求重心,重心做为树根,确保总体复杂度的那个logn
{
siz[u]=1;sonsiz[u]=0;
for(int v:p[u])
{
if(v==f||vis[v])continue;
getG(v,u);
siz[u]+=siz[v];
sonsiz[u]=max(sonsiz[u],siz[v]);
}
sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
if(sonsiz[u]<maxsiz)
{
root=u;maxsiz=sonsiz[u];
}
}
void getval(int u,int fa)
{
/*
做需要的计算
*/
for(int v:p[u])
{
if(v==fa||vis[v])continue;
getval(v,u);
}
}
void solve(int u)
{
//处理以u为根的数据
}
void divide(int u)//分治
{
vis[u]=1;
solve(u);
for(int v:p[u])
{
if(vis[v])continue;
maxsiz=inf;Tsiz=siz[v];getG(v,0);
divide(root);
}
}
例题
给定一棵有 \(n\) 个点的树,\(m\) 次询问树上距离为 \(k\) 的点对是否存在。\(1\le n\le10^4,\,m\le100\)
边 \(1\le w\le10000\),\(1\le k\le10^7\)。
\(code:\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e4+5;
void read(int&x)
{
char ch;
while(!isdigit(ch=getchar()));x=ch-'0';
while(isdigit(ch=getchar()))x=(x<<3)+(x<<1)+ch-'0';
}
struct P{
int u,w;
};
vector<P>p[maxn];
int Tsiz,siz[maxn],sonsiz[maxn],maxsiz;
bool vis[maxn];
int root;
void getG(int u,int f)
{
siz[u]=1;sonsiz[u]=0;
for(P poi:p[u])
{
if(poi.u==f||vis[poi.u])continue;
getG(poi.u,u);
siz[u]+=siz[poi.u];
sonsiz[u]=max(sonsiz[u],siz[poi.u]);
}
sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
if(sonsiz[u]<maxsiz)
{
root=u;maxsiz=sonsiz[u];
}
}
bool is[10000007];
int m,q[110],ans[110];
int dis[maxn],cnt,val[maxn];
void get(int u,int fa,int w)
{
if(w>10000000)return;
dis[++cnt]=w;val[u]=w;
for(P poi:p[u])
if(poi.u!=fa&&!vis[poi.u])
get(poi.u,u,w+poi.w);
}
void clear(int u,int fa)
{
is[val[u]]=0;val[u]=0;
for(P poi:p[u])
if(poi.u!=fa&&!vis[poi.u])
clear(poi.u,u);
}
void solve(int u)
{
is[0]=1;
for(P poi:p[u])
{
if(vis[poi.u])continue;
cnt=0;get(poi.u,u,poi.w);
for(int j=1;j<=m;j++)
for(int i=1;i<=cnt&&!ans[j];i++)
if(dis[i]<=q[j]&&is[q[j]-dis[i]])ans[j]=1;
for(int i=1;i<=cnt;i++)is[dis[i]]=1;
}
clear(u,0);
}
void divide(int u)
{
vis[u]=1;
solve(u);
for(P poi:p[u])
{
if(vis[poi.u])continue;
maxsiz=inf;Tsiz=siz[poi.u];getG(poi.u,0);
divide(root);
}
}
int main()
{
int n,u,v,w;
read(n);read(m);
for(int i=1;i<n;i++)
{
read(u);read(v);read(w);
p[u].push_back(P{v,w});p[v].push_back(P{u,w});
}
for(int i=1;i<=m;i++)read(q[i]);
Tsiz=n;maxsiz=inf;getG(1,0);divide(root);
for(int i=1;i<=m;i++)
{
if(ans[i])puts("AYE");
else puts("NAY");
}
}
2,CF990G
给定一棵有 \(n\) 个点的树,点有点权 \(a_i,\,1\le n,a_i\le2\times10^5\) ,
设 \(f(x,y)=gcd(a_x,a_y)\) 。
设函数 \(g(k)\) :
请求出所有 \(g(k),1\le k\le200000\) 。
当 \(g(k)\neq0\) 时,输出 \(g(k)\) 的值。
\(code:\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=2e5+5;
void read(int&x)
{
char ch;
while(!isdigit(ch=getchar()));x=ch-'0';
while(isdigit(ch=getchar()))x=(x<<3)+(x<<1)+ch-'0';
}
int a[maxn];
vector<int>p[maxn];
int Tsiz,siz[maxn],sonsiz[maxn],maxsiz;
bool vis[maxn];
int root;
void getG(int u,int f)
{
siz[u]=1;sonsiz[u]=0;
for(int v:p[u])
{
if(v==f||vis[v])continue;
getG(v,u);
siz[u]+=siz[v];
sonsiz[u]=max(sonsiz[u],siz[v]);
}
sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
if(sonsiz[u]<maxsiz)
{
root=u;maxsiz=sonsiz[u];
}
}
ll ans[maxn];
map<int,int>mp,tmp;
void get(int u,int fa,int w)
{
tmp[w]++;
for(int v:p[u])
{
if(v==fa||vis[v])continue;
get(v,u,__gcd(w,a[v]));
}
}
void solve(int u)
{
mp.clear();ans[a[u]]++;
for(int v:p[u])
{
if(vis[v])continue;
tmp.clear();get(v,u,__gcd(a[v],a[u]));
for(auto it:tmp)
{
ans[it.first]+=it.second;
for(auto itt:mp)
ans[__gcd(it.first,itt.first)]+=1ll*it.second*itt.second;
}
for(auto it:tmp)mp[it.first]+=it.second;
}
}
void divide(int u)
{
vis[u]=1;
solve(u);
for(int v:p[u])
{
if(vis[v])continue;
maxsiz=inf;Tsiz=siz[v];getG(v,0);
divide(root);
}
}
int main()
{
int n,u,v;
read(n);
for(int i=1;i<=n;i++)read(a[i]);
for(int i=1;i<n;i++)
{
read(u);read(v);
p[u].push_back(v);p[v].push_back(u);
}
Tsiz=n;maxsiz=inf;getG(1,0);divide(root);
for(int i=1;i<=200000;i++)
if(ans[i])printf("%d %lld\n",i,ans[i]);
}
点分树
点分树是通过更改原树形态使树的层数变成稳定 \(log\,n\) 的一种重构树 。
常用于解决与原树形态无关的带修改问题。
时间复杂度
重构树的层数约是 \(log\,n\) 层,(约?因为菊花图是1层.. 类推) ,设查询一个点的时间复杂度是 \(T_1\),则 \(T=T_1log\,n\)。
比如线段树+点分树,则时间复杂度是 \(\mathcal{O}(n(logn)^2)\)。
核心模板
bool vis[maxn];
int root,Tsiz,maxsiz,siz[maxn],sonsiz[maxn];
void getG(int u,int f)
{
siz[u]=1;sonsiz[u]=0;
for(int v:p[u])
{
if(v==f||vis[v])continue;
getG(v,u);
siz[u]+=siz[v];
sonsiz[u]=max(sonsiz[u],siz[v]);
}
sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
if(sonsiz[u]<maxsiz)
{
root=u;maxsiz=sonsiz[u];
}
}
int buildTree(int u,int f,int si)//最外层u任选,f置0,si为 n
{
Tsiz=si;maxsiz=inf;getG(u,0);
int g=root,tg,tsi;
vis[g]=1;fa[g]=f;tsiz[g]=si;
for(int v:p[g])
{
if(vis[v])continue;
tsi=siz[v]<siz[g]?siz[v]:(si-siz[g]);
tg=buildTree(v,g,tsi);
np[g].push_back(tg);
}
build(g,g);//这个是线段树的build,视具体操作
return g;
}
例题
1,BZOJ震波(luoguP6329【模板】点分树|震波) \((2s,250\rm{M})\)
大致题意:有 \(n\) 个点的无根树,每个点有点权,接下来要在线处理 \(m\) 个操作:
\(0\;x\;k\) :输出点 \(x\) 距离小于等于 \(k\) 内的所有点的权值和。
\(1\;x\;y\):表示把 点 \(x\) 的权值修改为 \(y\) 。
在线:数据的 \(x\;y\;k\;\) 需要异或上一次的答案。初始答案为 \(0\) 。
做法:
点分树+动态开点线段树+st表求lca
因为点分树是 \(log\,n\) 层 ,所以所有子树 点的个数之和 约为 \(nlog\,n\) , 所以对于每个点动态开点的建线段树,其空间复杂度还是很可观的。
至于 \(st\) 表求 \(lca\) , 因为 \(st\) 表求 \(lca\) 相比倍增和树剖求的 \(lca\) 来说,是最快的。
建两种线段树,点权均是距离, 其中 \(T_1\) 表示点 \(x\) 的子树内, 与 \(x\) 距离为 \(d\) 的点的点权和, \(T_2\)表示点 \(fa[x]\) 的子树内,与 \(fa[x]\) 距离为 \(d\) 的点的点权和。
\(code:\)
#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
#define reg register
#define min(a,b) (a)>(b)?(b):(a)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=2e5+5;
void read(int&x)
{
char c;
while(!isdigit(c=getchar()));x=c-'0';
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
}
int val[maxn];
vector<int>p[maxn],np[maxn];
int dep[maxn],depth[maxn<<1],id[maxn],rid[maxn<<1],cnt,st[maxn<<1][25];
void dfs(int u,int fa,int d)
{
id[u]=++cnt;rid[cnt]=u;depth[cnt]=d;dep[u]=d;
for(int v:p[u])
{
if(v==fa)continue;
dfs(v,u,d+1);
rid[++cnt]=u;depth[cnt]=d;
}
}
int lg[maxn<<1];
void init()
{
lg[0]=-1;
for(int i=1;i<=cnt;i++)lg[i]=lg[i>>1]+1;
for(int i=1;i<=cnt;i++)st[i][0]=i;
for(int j=1;(1<<j)<=cnt;j++)
for(int i=1;i+(1<<j)-1<=cnt;i++)
st[i][j]=depth[st[i][j-1]]<depth[st[i+(1<<j-1)][j-1]]?
st[i][j-1]:
st[i+(1<<j-1)][j-1];
}
int lca(int u,int v)
{
if(id[u]>id[v])swap(u,v);
int s=id[u],t=id[v],len=lg[t-s+1];
return depth[st[s][len]]<depth[st[t-(1<<len)+1][len]]?rid[st[s][len]]:rid[st[t-(1<<len)+1][len]];
}
int dis(int u,int v){return dep[u]+dep[v]-(dep[lca(u,v)]<<1);}
int T1[maxn],T2[maxn],tot;
int L[maxn<<4],R[maxn<<4],sum[maxn<<4];
void add(int &rt,int l,int r,int x,int v)
{
if(!rt)rt=++tot;
sum[rt]+=v;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)add(L[rt],l,mid,x,v);
else add(R[rt],mid+1,r,x,v);
}
int getv(int rt,int l,int r,int ll,int rr)
{
if(!rt||ll>r||rr<l)return 0;
if(ll<=l&&r<=rr)return sum[rt];
int mid=(l+r)>>1;
return getv(L[rt],l,mid,ll,rr)+getv(R[rt],mid+1,r,ll,rr);
}
int fa[maxn],tsiz[maxn];
void build(int u,int g)
{
add(T1[g],0,tsiz[g]-1,dis(u,g),val[u]);
if(fa[g])add(T2[g],0,tsiz[fa[g]]-1,dis(u,fa[g]),val[u]);
for(int v:np[u])
if(v!=fa[u])build(v,g);
}
bool vis[maxn];
int root,Tsiz,maxsiz,siz[maxn],sonsiz[maxn];
void getG(int u,int f)
{
siz[u]=1;sonsiz[u]=0;
for(int v:p[u])
{
if(v==f||vis[v])continue;
getG(v,u);
siz[u]+=siz[v];
sonsiz[u]=max(sonsiz[u],siz[v]);
}
sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
if(sonsiz[u]<maxsiz)
{
root=u;maxsiz=sonsiz[u];
}
}
int buildTree(int u,int f,int si)
{
Tsiz=si;maxsiz=inf;getG(u,0);
int g=root,tg,tsi;
vis[g]=1;fa[g]=f;tsiz[g]=si;
for(int v:p[g])
{
if(vis[v])continue;
tsi=siz[v]<siz[g]?siz[v]:(si-siz[g]);
tg=buildTree(v,g,tsi);
np[g].push_back(tg);
}
build(g,g);
return g;
}
int solveop0(int u,int s,int k,int x)
{
int d=dis(u,x),ans=0;
if(d<=k)
{
ans=getv(T1[u],0,tsiz[u]-1,0,k-d);
if(s)ans-=getv(T2[s],0,tsiz[u]-1,0,k-d);
}
if(fa[u])ans+=solveop0(fa[u],u,k,x);
return ans;
}
void solveop1(int u,int x,int w)//w=newval-val[u];val[u]=newval;
{
add(T1[u],0,tsiz[u]-1,dis(u,x),w);
if(fa[u])
{
add(T2[u],0,tsiz[fa[u]]-1,dis(fa[u],x),w);
solveop1(fa[u],x,w);
}
}
void printtree(int u,int d)
{
printf("root%d=(%d,fa:%d) ",d,u,fa[u]);
for(int v:np[u])
if(v!=fa[u])printtree(v,d+1);
putchar(10);
}
int main()
{
int n,m,u,v,op,ans=0;
read(n);read(m);
for(int i=1;i<=n;i++)read(val[i]);
for(int i=1;i<n;i++)
{
read(u);read(v);
p[u].push_back(v);p[v].push_back(u);
}
dfs(1,1,1);init();
int vr=buildTree(1,0,n);
// printtree(vr,1);
while(m--)
{
read(op);read(u);read(v);
u^=ans;v^=ans;
if(!op)
{
ans=solveop0(u,0,v,u);
printf("%d\n",ans);
}
else
{
solveop1(u,u,v-val[u]);
val[u]=v;
}
}
}
2,牛客 苹果树 \((4s,512\rm{M})\)
大致题意:有 \(n\) \((1\le n\le100000)\) 个点的无根树, 点有点权, 边有边权, 且权值 \(v\leq 10000\)。
\(m\) \((1\le m\le100000)\) 个询问:
\(1\;u\;x\):在点 \(u\) 处, 叠加一个点权为 \(x\) 的点;
\(2\;u\;x\;y\):询问从x出发,到点权值在 \([x,\;y]\) 范围内的一个点的最小距离花费。
做法:
点分树+动态开点线段树+st表求lca, 线段树的点值是点权, 线段树更新最小值,
\(ans=min(ans,dis(u,fa)+query(fa,l,r))\)
\(coed:\)
#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
#define reg register
#define min(a,b) (a)>(b)?(b):(a)
#define max(a,b) (a)>(b)?(a):(b)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+5;
const int MAXN=2e7;
void read(int&x)
{
char c;
while(!isdigit(c=getchar()));x=c-'0';
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
}
struct P{
int u,w;
};
const int up=10000;
int val[maxn];
struct EE{
P to;int next;
}egg[maxn<<1];
int headd[maxn],eecnt;
inline void add(int u,P v){egg[++eecnt]={v,headd[u]};headd[u]=eecnt;}
struct E{
int to,next;
}eg[maxn<<1];
int head[maxn],ecnt;
inline void add(int u,int v){eg[++ecnt]={v,head[u]};head[u]=ecnt;}
int dd[maxn],depth[maxn<<2],id[maxn],rid[maxn<<2],cnt,st[maxn<<2][25];
void dfs(int u,int fa,int d,int w)
{
id[u]=++cnt;rid[cnt]=u;depth[cnt]=d;dd[u]=w;
for(reg int i=headd[u];i;i=egg[i].next)
{
if(egg[i].to.u==fa)continue;
dfs(egg[i].to.u,u,d+1,w+egg[i].to.w);
rid[++cnt]=u;depth[cnt]=d;
}
}
int lg[maxn<<2];
void init()
{
lg[0]=-1;
for(reg int i=1;i<=cnt;i++)lg[i]=lg[i>>1]+1;
for(reg int i=1;i<=cnt;i++)st[i][0]=i;
for(reg int j=1;(1<<j)<=cnt;j++)
for(reg int i=1;i+(1<<j)-1<=cnt;i++)
st[i][j]=depth[st[i][j-1]]<depth[st[i+(1<<j-1)][j-1]]?
st[i][j-1]:st[i+(1<<j-1)][j-1];
}
inline int lca(int u,int v)
{
if(id[u]>id[v])swap(u,v);
reg int s=id[u],t=id[v],len=lg[t-s+1];
return depth[st[s][len]]<depth[st[t-(1<<len)+1][len]]?
rid[st[s][len]]:rid[st[t-(1<<len)+1][len]];
}
inline int dis(int u,int v){return dd[u]+dd[v]-(dd[lca(u,v)]<<1);}
int T[maxn],tot,L[MAXN],R[MAXN],mi[MAXN];
void add(int &rt,int l,int r,int x,int v)
{
if(!rt)rt=++tot,mi[rt]=inf;
mi[rt]=min(mi[rt],v);
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)add(L[rt],l,mid,x,v);
else add(R[rt],mid+1,r,x,v);
}
int getv(int rt,int l,int r,int ll,int rr)
{
if(!rt||ll>r||rr<l)return inf;
if(ll<=l&&r<=rr)return mi[rt];
int mid=(l+r)>>1,res;
res=getv(L[rt],l,mid,ll,rr);
if(R[rt]&&mi[R[rt]]<res)res=min(res,getv(R[rt],mid+1,r,ll,rr));
return res;
}
int fa[maxn];
void build(int u,int g)
{
add(T[g],1,up,val[u],dis(u,g));
for(int i=head[u];i;i=eg[i].next)build(eg[i].to,g);
}
bool vis[maxn];
int root,Tsiz,maxsiz,siz[maxn],sonsiz[maxn];
void getG(int u,int f)
{
siz[u]=1;sonsiz[u]=0;
for(reg int i=headd[u];i;i=egg[i].next)
{
if(egg[i].to.u==f||vis[egg[i].to.u])continue;
getG(egg[i].to.u,u);
siz[u]+=siz[egg[i].to.u];
sonsiz[u]=max(sonsiz[u],siz[egg[i].to.u]);
}
sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
if(sonsiz[u]<maxsiz)
{
root=u;maxsiz=sonsiz[u];
}
}
int buildTree(int u,int f,int si)
{
Tsiz=si;maxsiz=inf;getG(u,0);
int g=root,tg,tsi;
vis[g]=1;fa[g]=f;
for(reg int i=headd[g];i;i=egg[i].next)
{
if(vis[egg[i].to.u])continue;
tsi=siz[egg[i].to.u]<siz[g]?siz[egg[i].to.u]:(si-siz[g]);
tg=buildTree(egg[i].to.u,g,tsi);
add(g,tg);
}
build(g,g);
return g;
}
void solveop1(int u,int x,int w)
{
add(T[u],1,up,w,dis(u,x));
if(fa[u])solveop1(fa[u],x,w);
}
int solveop2(int u,int x,int l,int r)
{
int ans=dis(u,x)+getv(T[u],1,up,l,r);
if(fa[u])ans=min(ans,solveop2(fa[u],x,l,r));
return ans;
}
int main()
{
int n,m,u,v,w,op,ans;
read(n);read(m);
for(reg int i=1;i<=n;i++)read(val[i]);
for(reg int i=1;i<n;i++)
{
read(u);read(v);read(w);
add(u,{v,w});add(v,{u,w});
}
dfs(1,0,1,0);init();
buildTree(1,0,n);
while(m--)
{
read(op);read(u);read(v);
if(op==1)solveop1(u,u,v);
else
{
read(w);
ans=solveop2(u,u,v,w);
printf("%d\n",ans>=inf?-1:ans<<1);
}
}
}