dsu on tree

树上启发式合并。我并不知道为什么要叫做这个名字。。。

干什么的

可以在\(O(n\log n)\)的时间内完成对子树信息的询问,可横向对比把树按\(dfs\)序转成序列问题的\(O(n\sqrt n)\)莫队算法。

怎么实现

当\(dfs\)到一个点\(u\),执行以下操作:

1、递归处理所有轻儿子;

2、递归处理重儿子;

3、计算整棵子树的贡献(在第2步中重儿子的贡献得以保留,所以不需要重复计算);

4、若点\(u\)不是其父亲的重儿子,删除整棵子树的贡献。

看上去像是暴力?

复杂度分析

只有当存在一条轻边时,这条轻边所连接的子树才需要被重新计算一次贡献。那么一个点被重复计算的次数就等于这个点到根路径上经过的轻边条数。而根据轻重链剖分那套理论,一个点到根路径上的轻边不会超过\(\log n\)条,所以这个算法的复杂度为\(O(n\log n*t)\),其中\(t\)为一次计算贡献的复杂度。

题目

懒得分开写博客了qaq

CF600E Lomsat gelral

luogu

一棵树有\(n\)个结点,每个结点都有一种颜色,求每个子树中出现次数最多的颜色(们)的编号之和。

sol:维护\(num_i\)表示有多少种颜色出现了\(i\)次,\(sum_i\)表示这些颜色的编号和。显然计算贡献是\(O(1)\)的。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ll long long
const int N = 1e5+5;
int n,a[N],to[N<<1],nxt[N<<1],head[N],cnt;
int sz[N],son[N],vis[N],tot[N],num[N],Max;ll sum[N],ans[N];
void link(int u,int v){
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
void dfs1(int u,int f){
sz[u]=1;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=f){
dfs1(to[e],u);sz[u]+=sz[to[e]];
if (sz[to[e]]>sz[son[u]]) son[u]=to[e];
}
}
void update(int u,int f,int val){
int &t=tot[a[u]];
--num[t];sum[t]-=a[u];
t+=val;
++num[t];sum[t]+=a[u];
if (val>0) Max=max(Max,t);
else if (!num[Max]) --Max;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=f&&!vis[to[e]])
update(to[e],u,val);
}
void dfs(int u,int f,int keep){
for (int e=head[u];e;e=nxt[e])
if (to[e]!=f&&to[e]!=son[u])
dfs(to[e],u,0);
if (son[u]) dfs(son[u],u,1),vis[son[u]]=1;
update(u,f,1);
ans[u]=sum[Max];
vis[son[u]]=0;
if (!keep) update(u,f,-1);
}
int main(){
n=gi();
for (int i=1;i<=n;++i) a[i]=gi();
for (int i=1;i<n;++i){
int u=gi(),v=gi();
link(u,v);link(v,u);
}
dfs1(1,0);dfs(1,0,1);
for (int i=1;i<=n;++i) printf("%I64d ",ans[i]);
puts("");return 0;
}

CF570D Tree Requests

luogu

给你一棵\(n\)个节点的树,每个节点上有一个小写英文字母。每次询问\(v_i,h_i\),表示在\(v_i\)子树中所有深度为\(h_i\)的点上的字母拿出来重新组合能否形成一个回文串。

sol:一些字母重新组合后能够形成回文串当且仅当存在至多一个字母的出现次数为奇数。所以可以把每个小写英文字母当作是一个二进制位然后维护子树中每个深度的异或和即可。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 5e5+5;
struct node{int h,nxt;}a[N];
int n,m,nxt[N],head[N],val[N],ft[N];char s[N];
int dep[N],sz[N],son[N],vis[N],sum[N],ans[N];
void add(int v,int h,int id){
a[id]=(node){h,ft[v]};ft[v]=id;
}
void dfs1(int u,int f){
dep[u]=dep[f]+1;sz[u]=1;
for (int v=head[u];v;v=nxt[v]){
dfs1(v,u),sz[u]+=sz[v];
if (sz[v]>sz[son[u]]) son[u]=v;
}
}
void update(int u){
sum[dep[u]]^=val[u];
for (int v=head[u];v;v=nxt[v])
if (!vis[v]) update(v);
}
bool cal(int x){
int res=0;
while (x) ++res,x^=x&-x;
return res<=1;
}
void dfs(int u,int f,int keep){
for (int v=head[u];v;v=nxt[v])
if (v!=son[u]) dfs(v,u,0);
if (son[u]) dfs(son[u],u,1),vis[son[u]]=1;
update(u);
for (int i=ft[u];i;i=a[i].nxt) ans[i]=cal(sum[a[i].h]);
vis[son[u]]=0;
if (!keep) update(u);
}
int main(){
n=gi();m=gi();
for (int i=2,f;i<=n;++i)
f=gi(),nxt[i]=head[f],head[f]=i;
scanf("%s",s+1);
for (int i=1;i<=n;++i) val[i]=1<<s[i]-'a';
for (int i=1,v,h;i<=m;++i) v=gi(),h=gi(),add(v,h,i);
dfs1(1,0);dfs(1,0,1);
for (int i=1;i<=m;++i) puts(ans[i]?"Yes":"No");
return 0;
}

CF208E Blood Cousins

luogu

给你一片森林,每次询问和一个点有相同的\(k\)次祖先的点有多少个。

sol:倍增跳到那个祖先上然后就是只需要知道该深度有多少个点就行了。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 1e5+5;
int n,m,nxt[N],head[N],root[N],fa[18][N],ft[N];
int dep[N],sz[N],son[N],vis[N],tot[N],ans[N];
struct node{int d,nxt;}a[N];
void add(int x,int d,int id){
a[id]=(node){d,ft[x]};ft[x]=id;
}
void dfs1(int u,int f){
fa[0][u]=f;dep[u]=dep[f]+1;sz[u]=1;
for (int i=1;i<=17;++i) fa[i][u]=fa[i-1][fa[i-1][u]];
for (int v=head[u];v;v=nxt[v]){
dfs1(v,u);sz[u]+=sz[v];
if (sz[v]>sz[son[u]]) son[u]=v;
}
}
void update(int u,int val){
tot[dep[u]]+=val;
for (int v=head[u];v;v=nxt[v])
if (!vis[v]) update(v,val);
}
void dfs(int u,int keep){
for (int v=head[u];v;v=nxt[v])
if (v!=son[u]) dfs(v,0);
if (son[u]) dfs(son[u],1),vis[son[u]]=1;
update(u,1);
for (int i=ft[u];i;i=a[i].nxt) ans[i]=tot[a[i].d]-1;
vis[son[u]]=0;
if (!keep) update(u,-1);
}
int main(){
n=gi();
for (int i=1;i<=n;++i){
int f=gi();
if (!f) root[++root[0]]=i;
else nxt[i]=head[f],head[f]=i;
}
for (int i=1;i<=root[0];++i) dfs1(root[i],0);
m=gi();
for (int i=1;i<=m;++i){
int x=gi(),k=gi(),d=dep[x];
for (int j=0;j<=17;++j) if (k&(1<<j)) x=fa[j][x];
add(x,d,i);
}
for (int i=1;i<=root[0];++i) dfs(root[i],0);
for (int i=1;i<=m;++i) printf("%d ",ans[i]);
puts("");return 0;
}

最新文章

  1. Linux下VI命令详细介绍
  2. 【转】STM32中的抢占优先级、响应优先级概念
  3. Android JPush(极光推送)的使用教程
  4. ActionScript语言函数重载
  5. 常用的sql语句(找不同位数,找重复)
  6. Hbase之原子性插入
  7. SPOJ375(树链剖分)
  8. hdu2899 Strange fuction
  9. Linux kernel Vhost-net 和 Virtio-net代码详解
  10. Javasript 正则匹配任意字符
  11. 北京师范大学校赛C
  12. shell脚本学习-文件包含
  13. 【转载】论文笔记系列-Tree-CNN: A Deep Convolutional Neural Network for Lifelong Learning
  14. ESP8266使用笔记之常用固件
  15. php多进程 防止出现僵尸进程
  16. SVN服务器搭建和使用以及冲突解决、用户密码修改
  17. Windows批处理命令初了解
  18. CentOS 7 SSH远程证书登陆
  19. css ! important 兼容性的一点测试
  20. IE8 td元素 width无效的bug;

热门文章

  1. BZOJ 1002 轮状病毒 矩阵树定理
  2. BZOJ 1051 受欢迎的牛 缩点
  3. (七)Linux下的关机与重启命令
  4. Web项目打成war包部署到tomcat时报MySQL Access denied for user &#39;root&#39;@&#39;localhost&#39; (using password: YES)错误解决方案
  5. 2019.3.6 Github学习 &amp;Git学习
  6. 洛谷P2336 [SCOI2012]喵星球上的点名(后缀数组+莫队)
  7. PAT——1060. 爱丁顿数
  8. PAT——1040. 有几个PAT
  9. HDU 1301Jungle Roads(最小生成树 prim,输入比较特殊)
  10. EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解