树上启发式合并
树上启发式合并,\(DSU\) \(On\) \(Tree\),静态链分治,用于求解支持离线的树上子树查询问题。
暴力做法,每次做完一棵树就要把它清空,避免对它兄弟造成影响,但是做到它的祖先时又会重新对它做一遍,发现最后一棵树是不需要清空的,于是考虑将节点数最多的留到最后。
首先对树进行轻重链剖分,找出重儿子,在计算答案时,先遍历\(x\)的轻儿子,但不保留其影响,之后遍历\(x\)的重儿子,保留影响,最后再次遍历\(x\)的轻儿子,得到\(x\)的答案。
除了重儿子,每次遍历完后要恢复之前的状态。
每次询问一个点x和k,问由多少个点和x由共同的k级祖先。
倍增预处理,将询问离线到x的k级祖先,用cnt数组记录当前子树内不同深度的出现次数。
inline int find(int x,int k){
for(int i=18;~i;i--)if(k>=1<<i)k-=1<<i,x=fa[x][i];
/* for(int i=18;~i;i--)if(k>=dep[x]-dep[fa[x][i]])k-=dep[x]-dep[fa[x][i]],x=fa[x][i];效果一样*/
return x;
}
void dsu(int x,bool isson){
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x])continue;
dsu(y,false);/*先遍历轻儿子*/
}
if(son[x])dsu(son[x],true);/*有重儿子则遍历重儿子*/
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x])continue;
for(int j=ipt[y];j<=ipt[y]+sz[y]-1;j++)cnt[dep[mp[j]]]++;/*dfs序映射子树内节点,深度更新*/
}
cnt[dep[x]]++;/*更新x自己的深度*/
for(auto a:v[x])ans[a.id]=cnt[dep[x]+a.k]-1;/*询问点的k级祖先为x,即x的k级后代为询问点,查询相同深度的点的个数并减去询问自己*/
if(isson==false)for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)cnt[dep[mp[i]]]--;/*复原*/
}
每个点有颜色,求每棵子树占据主导地位的颜色的编号和。
为每个颜色开一个桶,记录当前最大值和\(sum\),每次由更新操作时比较\(cnt\)和最大值,相等则累加\(sum\),cnt大于最大值时更新最大值并重置\(sum\),清除轻儿子贡献时清空最大值和\(sum\).
void dsu(int x,bool isson){
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x]||y==fa[x])continue;
dsu(y,false);
}
if(son[x])dsu(son[x],true);
cnt[col[x]]++;
if(cnt[col[x]]>ma)ma=cnt[col[x]],sum=col[x];
else if(cnt[col[x]]==ma)sum+=col[x];
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x]||y==fa[x])continue;
for(int j=ipt[y];j<=ipt[y]+sz[y]-1;j++){
cnt[col[mp[j]]]++;
if(cnt[col[mp[j]]]>ma)ma=cnt[col[mp[j]]],sum=col[mp[j]];
else if(cnt[col[mp[j]]]==ma)sum+=col[mp[j]];
}
}
ans[x]=sum;
if(isson==false){
sum=ma=0;
for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)cnt[col[mp[i]]]--;
}
}
每个点有点权,定义一棵树合法当且仅当不存在简单路径的异或和为0,每次可以改变一个点权成为任意数,求最少操作数使得树合法。
假设将\(x\)的点权修改为\(2^{30+x}\),那么所有经过\(x\)的路径的异或和都不会为\(0\)。为每一个点开一个集合存子树内所有点的\(dis\),即\(1\)到该点的异或和,枚举\(x\)的儿子\(y\),将\(y\)的集合合并到\(x\)上面,强制\(x\)的\(size\)大于\(y\)的\(size\),枚举\(y\)中每一个元素\(k\),若在\(x\)中可以找到\(a[x]\) \(xor\) \(k\),那么\(x\)需要修改,之后将\(x\)的集合清空。
void dfs(int x,int fa){
mp[x][dis[x]]=true;
bool flag=false;
for(auto y:v[x]){
if(y==fa)continue;
dis[y]=dis[x]^a[y];
dfs(y,x);
if(mp[x].size()<mp[y].size())swap(mp[x],mp[y]);
for(auto k:mp[y])if(mp[x].find(a[x]^k.first)!=mp[x].end()){
flag=true;
break;
}
for(auto k:mp[y])mp[x][k.first]=true;
}
if(flag==true)mp[x].clear(),ans++;
}
一片森林,每个点有一个字符串作为名字,每次询问点\(x\)的\(k\)级子孙共有多少个不同的名字。
\(unordered\_map\)维护同一个深度的字符串集合,撤销轻儿子操作时则清空它。
void dsu(int x,bool isson){
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x])continue;
dsu(y,false);
}
if(son[x])dsu(son[x],true);
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x])continue;
for(int j=ipt[y];j<=ipt[y]+sz[y]-1;j++)cnt[dep[mp[j]]][s[mp[j]]]=true;
}
cnt[dep[x]][s[x]]=true;
for(auto a:v[x])ans[a.id]=cnt[a.x].size();
if(isson==true)return;
for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)cnt[dep[mp[i]]].clear();
}
for(int i=1;i<=m;i++){
int x,k;
cin>>x>>k;
v[x].push_back({i,dep[x]+k});
}
询问一个点的子树内出现次数 \(>=k\) 的颜色种类。
用 \(cnt[x]\) 表示颜色 \(x\) 的出现次数,\(sum[x]\) 表示出现次数 \(>=x\) 的颜色种类数,在添加颜色时,两个桶都是增加的,\(sum[x]\) 相当于一个前缀和。
void dsu(int x,bool isson){
for(auto y:v[x]){
if(y==fa[x]||y==son[x])continue;
dsu(y,false);
}
if(son[x])dsu(son[x],true);
for(auto y:v[x]){
if(y==fa[x]||y==son[x])continue;
for(int i=ipt[y];i<=ipt[y]+sz[y]-1;i++)sum[++cnt[col[mp[i]]]]++;
}
sum[++cnt[col[x]]]++;
for(auto a:q[x])ans[a.id]=sum[a.k];
if(isson==true)return;
for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)--sum[cnt[col[mp[i]]]--];
}
询问一个点的子树内给定深度的点重排后能否组成回文串。
每一个深度开一个桶进行维护,奇数个数 \(<=1\) 则可以。
void dsu(int x,bool isson){
for(auto y:v[x])if(y^son[x])dsu(y,false);
if(son[x])dsu(son[x],true);
for(auto y:v[x]){
if(y==son[x])continue;
for(int i=ipt[y];i<=ipt[y]+sz[y]-1;i++)cnt[dep[mp[i]]][s[mp[i]]-'a']++;
}
cnt[dep[x]][s[x]-'a']++;
for(auto a:q[x]){
int num=0;
for(int i=0;i<26;i++)num+=cnt[a.x][i]&1;
ans[a.id]=num<=1;
}
if(isson==true)return;
for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)cnt[dep[mp[i]]][s[mp[i]]-'a']--;
}
也可以 \(O(1)\) 判断,利用二进制,若 \(x==(x\&-x)\) 则说明 \(x\) 全为 \(0\) 或 \(x\) 的二进制下只有一位 \(1\).
void dsu(int x,bool isson){
for(auto y:v[x])if(y^son[x])dsu(y,false);
if(son[x])dsu(son[x],true);
for(auto y:v[x]){
if(y==son[x])continue;
for(int i=ipt[y];i<=ipt[y]+sz[y]-1;i++)check[dep[mp[i]]]^=1<<s[mp[i]]-'a';
}
check[dep[x]]^=1<<s[x]-'a';
for(auto a:q[x])ans[a.id]=check[a.x]==(check[a.x]&-check[a.x]);
if(isson==true)return;
for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)check[dep[mp[i]]]^=1<<s[mp[i]]-'a';
}
定义 \(d(x,k)\) 为 \(x\)子树内到 \(x\) 距离为 \(k\) 的点的数量,求一个最小的 \(k\) 使得 每一个 \(x\) 最大。
维护一个最大数量的深度,更新时若数量大于当前深度对应的数量则更新,或者相等且深度较浅也更新,答案就是当前深度减去当前点的深度。
void dsu(int x,bool isson){
for(auto y:v[x]){
if(y==fa[x]||y==son[x])continue;
dsu(y,false);
}
if(son[x])dsu(son[x],true);
for(auto y:v[x]){
if(y==fa[x]||y==son[x])continue;
for(int i=ipt[y];i<=ipt[y]+sz[y]-1;i++){
cnt[dep[mp[i]]]++;
if(cnt[dep[mp[i]]]>cnt[now]||(cnt[dep[mp[i]]]==cnt[now]&&now>dep[mp[i]]))now=dep[mp[i]];
}
}
cnt[dep[x]]++;
if(cnt[dep[x]]>cnt[now]||(cnt[dep[x]]==cnt[now]&&now>dep[x]))now=dep[x];
ans[x]=now-dep[x];
if(isson==true)return;
now=0;
for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)cnt[dep[mp[i]]]--;
}
CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
求每个点的子树内能够重排后形成回文串的简单路径的最大长度,边权为 \(a-v\).
边权是字符,可以看做 \(2^{ch-'a'}\) 来连边,合法的状态只有23种,为每个状态开一个桶,\(buc[x]\) 表示当前子树内,从 \(1\) 到子树内异或路径状态为 \(x\) 的节点的最大深度。
void dsu(int x,bool isson){
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x])continue;
dsu(y,false);
ans[x]=max(ans[x],ans[y]);
}
if(son[x])dsu(son[x],true),ans[x]=max(ans[x],ans[son[x]]);
if(buc[dis[x]])ans[x]=max(ans[x],buc[dis[x]]-dep[x]);
for(int i=0;i<22;i++)if(buc[dis[x]^1<<i])ans[x]=max(ans[x],buc[dis[x]^1<<i]-dep[x]);
buc[dis[x]]=max(buc[dis[x]],dep[x]);
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son[x])continue;
for(int j=ipt[y];j<=ipt[y]+sz[y]-1;j++){
if(buc[dis[mp[j]]])ans[x]=max(ans[x],buc[dis[mp[j]]]+dep[mp[j]]-dep[x]*2);
for(int k=0;k<22;k++)if(buc[dis[mp[j]]^1<<k])ans[x]=max(ans[x],buc[dis[mp[j]]^1<<k]+dep[mp[j]]-dep[x]*2);
}
for(int j=ipt[y];j<=ipt[y]+sz[y]-1;j++)buc[dis[mp[j]]]=max(buc[dis[mp[j]]],dep[mp[j]]);
}
if(isson==true)return;
for(int i=ipt[x];i<=ipt[x]+sz[x]-1;i++)buc[dis[mp[i]]]=0;
}

浙公网安备 33010602011771号