Dsu on tree 神奇的暴力
2017-03-21 21:53
204 查看
什么是dsu
这是一个很暴力很无脑的算法。对于一棵树如果我们需要计算每个节点对应子树的信息。
由于每个父节点的信息来自每个子节点。我们来用以下的流程来合并信息。
为什么可以用dsu
显然对于一个节点它只会被合并(logn)次所以复杂度可以为n∗(logn)例如我们要:统计子树内出现次数不少于k的元素个数。
void dfs_pre(int x,int f){ sz[x]=1; L[x]=++num; dfsline[num]=col[x]; for(int i=0;i<G[x].size();++i){ int y=G[x][i]; if(y==f)continue; dfs_pre(y,x); sz[x]+=sz[y]; }R[x]=num; } void Del(int x){ for(int i=Lt[x];i<=Rt[x];i++){ int t=dfsLine[i]; --Res[cnt[col[t]]--]; } } void Add(int x){ for(int i=Lt[x];i<=Rt[x];i++) { int t=dfsLine[i]; ++Res[++cnt[col[t]]]; } } void dsu(int x) { 找到重儿子son[x] for(y is son of x){ if(y!=son[x])dsu(y),Del(y); //解决了y的信息后从容器中删去y } dsu(son[x]);//不删除big对应的子树 for(y is son of x){ if(y!=son[x])Add(y)加入y这棵子树 } 加入x节点 回答在x节点上的询问 }
怎么用dsu
<1>我们发现我们一共只使用一个容器来维护size最大的元素的信息同样,我们可以使用其他数据结构,不一定是数组。<2>如果题目的信息对其父节点的权值或性质没有要求我们就可以使用dsu搜集并计算。
举几个例子
<1>给定一棵树,每个节点有颜色c[i],定义子树u中出现次数最多的颜色为u的主要颜色(可以有多个),求每个节点的主要颜色数值和。我们维护一个最大值mx表次数最多的颜色的次数以及一个cnt[x]记录每个颜色的个数,为了O(1)的求出答案我们用res[x]记录出现次数为x的答案。
void Add(int x){ for(int i=L[x];i<=R[x];++i){ res[++cnt[dfsline[i]]]+=dfsline[i]; Max(mx,cnt[dfsline[i]]); } } void Del(int x){ for(int i=L[x];i<=R[x];++i){ if(cnt[dfsline[i]]==mx)--mx; res[cnt[dfsline[i]]--]-=dfsline[i]; } } void dsu(int x,int f){ mx=0; for(int i=0;i<G[x].size();++i){ int y=G[x][i]; if(y!=f&&y!=big){ dsu(y,x);Del(y); } }if(son[x])dsu(big,x); for(int i=0;i<G[x].size();++i){ int y=G[x][i]; if(y!=f&&y!=son[x])Add(y); } res[++cnt[col[x]]]+=col[x]; Max(mx,cnt[col[x]]); ans[x]=res[mx]; } void Solve(){ dfs_pre(1,0); dsu(1,0); for(int i=1;i<=n;i++) cout<<ans[i]<<" "; }
<2>给定一棵树,已知每个节点的父亲节点和每个节点上的字符(给出一条字符串,全部为小写字母)。对于m个询问,判断能否排列属于子树x的,深度为d的节点上所有的字符,以组成一个回文串。
由于这次的询问是对一个子树一个深度上信息的询问所以我们可以直接用一个二进制数组res[dep]来储存信息。
bool check(int x){ int res=0; while(x){ if(x&1)res++; x>>=1; }return res<=1; } void update(int x){ for(int i=L[x];i<=R[x];i++){ int t=dfsline[i]; res[dep[t]]^=1<<col[t]; } } void dsu(int x,int f){ for(int i=0;i<G[x].size();++i){ int y=G[x][i]; if(y!=f&&y!=big){ solve(y,x);update(y); } } if(big)solve(big,x); for(int i=0;i<G[x].size();++i){ int y=G[x][i]; if(y!=f&&y!=big)update(y); } res[dep[x]]^=1<<col[x]; for(int i=0;i<Q[x].size();i++) ans[Q[x][i].id]=check(res[Q[x][i].d]); } void solve(){ init(); dfs_pre(); dsu(1,0); put_ans(); }
<3>给定一棵以1为根的带权树,求满足w[LCA(u,v)]=w[u]×w[v]w[LCA(u,v)]=w[u]×w[v]的{u,v}点对个数。({u,v}={v,u},u≠≠v)
很容易发现只要我们把子树依次放入容器后,再把每个个子树上的信息和其他子树上的信息合并。
即用cnt[x]记录x出现的次数,对于一个需要加入的子树我们把(val[fa]=x*y)cnt[x]*son_tree_imformation[y]更新答案。
inline void Del(int x){ for(int i=L[x];i<=R[x];i++) --cnt[col[dfsline[i]]]; } inline void Add(int x,int nw){ for(int i=L[x];i<=R[x];i++){ int t=col[dfsline[i]]; if(nw%T[t]==0){ int rank=lower_bound(T+1,T+1+m,nw/T[t])-T; if(rank<=m&&T[rank]==nw/T[t])ans+=cnt[rank]; } } for(int i=L[x];i<=R[x];i++) ++cnt[col[dfsline[i]]]; } void solve(int x,int f){ int big=0; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(f==y)continue; if(sz[big]<sz[y])big=y; } for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y!=f&&y!=big){ solve(y,x);Del(y); } } if(big)solve(big,x); for(int i=0;i<G[x].size();i++){ int y=G[x][ d6dd i]; if(y!=f&&y!=big)Add(y,T[col[x]]); } if(T[1]==1)ans+=cnt[1]; cnt[col[x]]++; } int main(){ int a,b;Rd(n); for(int i=1;i<n;i++){ Rd(a);Rd(b); G[a].push_back(b); G.push_back(a); } for(int i=1;i<=n;i++){ Rd(col[i]); T[i]=col[i]; } sort(T+1,T+n+1); m=unique(T+1,T+n+1)-T-1; for(int i=1;i<=n;i++) col[i]=lower_bound(T+1,T+1+m,col[i])-T; dfs_pre(1,0); solve(1,0); cout<<ans; } 也可以用启发式和并 #define f first #define s second #define pb push_back typedef long long ll; using namespace std; ll A[100011]; vector<int>G[100011]; map<ll,ll>res[100011]; ll ans=0; int cnt=0; int dfs(int v,int p){//返回v所存储的下表 cnt++; int big = v; res[v][A[v]]++; ll cur=0; for(int i=0;i<G[v].size();i++){ if(G[v][i]!=p){ int x = dfs(G[v][i],v); if(res[big].size()<res[x].size())swap(big,x); for(map<ll,ll>::iterator it=res[x].begin();it!=res[x].end();it++) if(A[v]%it->f==0)//找原子树中没有的元素 if(res[big].find(A[v]/it->f)!=res[big].end()) ans+=it->s *res[big][A[v]/it->f]; for(map<ll,ll>::iterator it=res[x].begin();it!=res[x].end();it++) res[big][it->f]+=it->s; } } return big; } int main(){ int n,u,v; cin >> n; for(int i=1;i<n;i++){ cin>>u>>v; G[u].pb(v); G[v].pb(u); } for(int i=1;i<=n;i++)cin>>A[i]; dfs(1,0); cout<<ans; }
问一个树上在它深度d以下节点的个数。
[b]用Bit来维护即可
inline void add(int i,int v){ while(i<=n)Bit[i]+=v,i+=i&-i; } inline int sum(int i){ int res=0; while(i)res+=Bit[i],i-=i&-i; return res; } void dfs(int x,int f){ dep[x]=dep[f]+1; for(register int i=H[x];i;i=T[i]) ans[id[i]]-=sum(d[i]+dep[x]); add(dep[x],val[x]); for(register int i=h[x];i;i=nx[i])dfs(G[i],x); for(register int i=H[x];i;i=T[i]) ans[id[i]]+=sum(d[i]+dep[x]); } int main(){ Rd(n);Rd(m); for(int i=1;i<=n;i++) Rd(val[i]); for(int i=2;i<=n;i++){ Rd(a); p_G(a,i); } for(int i=1;i<=m;i++){ Rd(a);Rd(b); p_Q(a,i,b); } dfs(1,0); for(int i=1;i<=m;i++) printf("%d\n",ans[i]); }
相关文章推荐
- codeforces 246 E. Blood Cousins Return (set+dsu on the tree)
- bzoj 3681: Arietta 主席树优化建图+网络流+dsu on tree
- dsu on tree(树上启发式合并)简介(codeforces 600 E)
- 【NOIP2017提高A组模拟9.14】生命之树 (dsu on tree+trie)
- [codeforces570D]Tree Requests(dsu on the tree)
- [Codeforces741D]Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(dsu on the tree)
- [dsu on tree] Codeforces #741D. Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths
- [dsu on tree 主席树优化建图 最大流] BZOJ 3681 Arietta
- Codechef TREEPATH 线段树优化dp+dsu on tree
- 树上启发式合并 DSU On Tree
- [Codeforces375D]Tree and Queries(dsu on the tree+bit)
- 【学习笔记】dsu on tree
- [二进制分组 dsu on tree 二次函数] Codechef KILLER Painting Tree
- codeforces 741 D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths (dsu on the tree)
- 【CodeForces】600 E. Lomsat gelral (dsu on tree)
- [Dsu on tree]CodeForces 600 E
- CF 741D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths [dsu on tree 类似点分治]
- 【dsu on tree】Codeforces741D[Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths]题解
- [codeforces246E]Blood Cousins Return(dsu on the tree+STL)
- dsu on tree