Dsu on Tree总结
Dsu on Tree(树上启发式合并)
关于Dsu?
并查集(亦称Ufs)。
然而本算法和并查集并没有半毛钱关系。
有什么用?
可以在\(O(nlogn)\)的时间复杂度内解决大部分不带修改的子树信息查询问题。
算法过程?
一个dfs,可能还需要写一个辅助函数Add。
简述一下算法过程:
假设现在dfs到了\(x\)结点。
-
dfs进入轻子树,离开这棵轻子树后从统计数组中暴力清除这棵轻子树中的信息(用另一个dfs(即Add,参数\(kk=-1\))递归遍历整棵轻子树并从统计数组中清除每个结点的信息)。
-
dfs进入重子树,结束后不清除这棵重子树的信息(可以认为\(x\)继承了其重儿子\(heavychild[x]\)的信息)。
-
用另一个dfs(即Add,参数\(kk=+1\))暴力统计x的所有轻子树的信息并计入统计数组。
-
此时统计数组内的信息即为以\(x\)为根的子树的信息,此时可以回答所有关于子树\(x\)的询问。
这个算法好暴力!
辣鸡博主别欺负我读书少,这不是\(O(n^2)\)的吗?
别急,我们来冷静分析一下这个算法的时间复杂度。
显然,如果只考虑dfs1和的dfs2(不考虑其中的Add),这个算法显然是\(O(n)\)的。
现在我们来看看一个结点\(x\)会被哪些结点出发的Add递归访问到。
显然如果结点\(x\)会被从结点\(y\)出发的Add递归访问到,\(y\)必须是\(x\)到根路径上的结点。
分析算法过程,我们可以发现,Add的出发节点必须作为其父结点的一个轻儿子,即只可能是其所在重链的顶端结点。
考虑到任何一个结点到根结点的路径上最多只有\(logn\)条重链,也就是最多有\(logn\)个合法的Add的出发结点,因此每个结点最多被Add访问到\(O(logn)\)次。自然,算法总时间复杂度\(O(nlogn)\)得证。
程序实现?
dfs1求出每个结点的重儿子
这里的实现方法和树剖完全相同。
void dfs1(int x,int depth){
dep[x]=depth;
siz[x]=1;
int maxsiz=-1;
trav(i,x){
int ver=e[i].to;
dfs1(ver,depth+1);
siz[x]+=siz[ver];
if(siz[ver]>maxsiz){
maxsiz=siz[ver];
pc[x]=ver;
}
}
}
dfs2统计答案
void Add(int x,int kk){
if(kk==1){
统计这个结点的贡献;
}
else if(kk==-1){
清除这个结点的贡献;
(这里可以直接清空深度为dep[x]的统计数组)
}
trav(i,x){
int ver=e[i].to;
if(mark[ver]) continue;
Add(ver,kk);
}
}
void dfs2(int x,bool Keep){
trav(i,x){
int ver=e[i].to;
if(ver==pc[x]) continue;
dfs2(ver,0);
}//1
if(pc[x]) dfs2(pc[x],1);//2
mark[pc[x]]=1;
Add(x,1);//3(这里通过标记重儿子可防止Add进入x的重子树,相当于以下注释代码)
// trav(i,x){
// int ver=e[i].to;
// if(ver==pc[x]) continue;
// Add(ver,1);
// }
mark[pc[x]]=0;
rin(i,0,(int)vec[x].size()-1)
ans[vec[x][i].id]=...;//4(用std::vector储存结点x上的询问
if(!Keep) Add(x,-1);//这里不能memset,否则会TLE
}
一些其它的写法(摘自Codeforces)
vector<int> *vec[maxn];
int cnt[maxn];
void dfs(int v, int p, bool keep){
int mx = -1, bigChild = -1;
for(auto u : g[v])
if(u != p && sz[u] > mx)
mx = sz[u], bigChild = u;
for(auto u : g[v])
if(u != p && u != bigChild)
dfs(u, v, 0);
if(bigChild != -1)
dfs(bigChild, v, 1), vec[v] = vec[bigChild];
else
vec[v] = new vector<int> ();
vec[v]->push_back(v);
cnt[ col[v] ]++;
for(auto u : g[v])
if(u != p && u != bigChild)
for(auto x : *vec[u]){
cnt[ col[x] ]++;
vec[v] -> push_back(x);
}
//now (*cnt[v])[c] is the number of vertices in subtree of vertex v that has color c. You can answer the queries easily.
// note that in this step *vec[v] contains all of the subtree of vertex v.
if(keep == 0)
for(auto u : *vec[v])
cnt[ col[u] ]--;
}
int cnt[maxn];
void dfs(int v, int p, bool keep){
int mx = -1, bigChild = -1;
for(auto u : g[v])
if(u != p && sz[u] > mx)
mx = sz[u], bigChild = u;
for(auto u : g[v])
if(u != p && u != bigChild)
dfs(u, v, 0); // run a dfs on small childs and clear them from cnt
if(bigChild != -1)
dfs(bigChild, v, 1); // bigChild marked as big and not cleared from cnt
for(auto u : g[v])
if(u != p && u != bigChild)
for(int p = st[u]; p < ft[u]; p++)
cnt[ col[ ver[p] ] ]++;
cnt[ col[v] ]++;
//now cnt[c] is the number of vertices in subtree of vertex v that has color c. You can answer the queries easily.
if(keep == 0)
for(int p = st[v]; p < ft[v]; p++)
cnt[ col[ ver[p] ] ]--;
}
例题?
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#define rin(i,a,b) for(int i=(a);i<=(b);i++)
#define rec(i,a,b) for(int i=(a);i>=(b);i--)
#define trav(i,a) for(int i=head[(a)];i;i=e[i].nxt)
using std::cin;
using std::cout;
using std::endl;
typedef long long LL;
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x;
}
const int MAXN=500005;
int n,m;
int ecnt,head[MAXN];
int dep[MAXN],siz[MAXN],pc[MAXN];
int cnt[MAXN][30],cnt1[MAXN];
bool mark[MAXN],ans[MAXN];
char ch[MAXN];
struct Edge{
int to,nxt;
}e[MAXN<<1];
struct Quest{
int d,id;
};
std::vector<Quest> vec[MAXN];
inline void add_edge(int bg,int ed){
ecnt++;
e[ecnt].to=ed;
e[ecnt].nxt=head[bg];
head[bg]=ecnt;
}
void dfs1(int x,int depth){
dep[x]=depth;
siz[x]=1;
int maxsiz=-1;
trav(i,x){
int ver=e[i].to;
dfs1(ver,depth+1);
siz[x]+=siz[ver];
if(siz[ver]>maxsiz){
maxsiz=siz[ver];
pc[x]=ver;
}
}
}
void Add(int x,int kk){
// if(kk==1){
cnt[dep[x]][ch[x]-'A']+=kk;
if(cnt[dep[x]][ch[x]-'A']&1) cnt1[dep[x]]++;
else cnt1[dep[x]]--;
// }
// else{
// cnt[dep[x]][ch[x]-'A']=0;
// cnt1[dep[x]]=0;
// }
//这里去掉注释也对
trav(i,x){
int ver=e[i].to;
if(mark[ver]) continue;
Add(ver,kk);
}
}
void dfs2(int x,bool Keep){
if(pc[x]) dfs2(pc[x],1);
mark[pc[x]]=1;
Add(x,1);
mark[pc[x]]=0;
trav(i,x){
int ver=e[i].to;
if(ver==pc[x]) continue;
dfs2(ver,0);
}
rin(i,0,(int)vec[x].size()-1)
ans[vec[x][i].id]=(cnt1[vec[x][i].d]<2);
// if(!Keep){
// memset(cnt,0,sizeof cnt);
// memset(cnt1,0,sizeof cnt1);
// }
//这里去掉注释并把下面加上注释也对,但是会T
if(!Keep) Add(x,-1);
}
int main(){
n=read(),m=read();
rin(i,2,n){
int u=read();
add_edge(u,i);
}
rin(i,1,n){
char temp=getchar();
while(!isalpha(temp)) temp=getchar();
ch[i]=temp;
}
rin(i,1,m){
int x=read(),d=read();
vec[x].push_back((Quest){d,i});
}
dfs1(1,1);
dfs2(1,1);
rin(i,1,m){
if(ans[i]) printf("Yes\n");
else printf("No\n");
}
return 0;
}
posted on 2018-11-22 20:13 ErkkiErkko 阅读(195) 评论(0) 编辑 收藏 举报