树上启发式合并 (dsu on tree)
这个故事告诉我们,在做一个辣鸡出题人的比赛之前,最好先看看他发明了什么新姿势= =居然直接出了道裸题
参考链接:
http://codeforces.com/blog/entry/44351(原文)
http://blog.csdn.net/QAQ__QAQ/article/details/53455462
这种技巧可以在O(nlogn)的时间内解决绝大多数的无修改子树询问问题。
例1 子树颜色统计
有一棵n个点的有根树,根为1,每个点有一个1~n的颜色,对于每一个点给了一个数k,要询问这个子树中颜色为k的点的个数。n<=500000。
这个例子当然过于trivial,dfs序完一棵主席树就能水过去,不过这不是重点...
我们的目标就是实现一个不那么暴力的东西,可以代替以下代码:
Edg int n,cc[SZ],col[SZ],ks[SZ],anss[SZ];
void edt(int x,int f,int v)
{
cc[col[x]]+=v;
for esb(x,e,b)
if(b!=f) edt(b,x,v);
}
void dfs(int x,int f=0)
{
edt(x,f,1);
anss[x]=cc[ks[x]];
edt(x,f,-1);
for esb(x,e,b)
if(b!=f) dfs(b,x);
}
输入大致如下:
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",col+i);
for(int i=1;i<=n;i++) scanf("%d",ks+i);
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
adde(a,b);
}
dfs(1);
for(int i=1;i<=n;i++) printf("%d\n",anss[i]);
}
以下是一个清真的nlogn做法:
Edg
int n,cc[SZ],col[SZ],ks[SZ],anss[SZ];
int sz[SZ],son[SZ];
void gs(int x,int f=0)
{
sz[x]=1;
for esb(x,e,b)
{
if(b==f) continue;
gs(b,x); sz[x]+=sz[b];
if(sz[b]>sz[son[x]]) son[x]=b;
}
}
int skip=0;
void edt(int x,int f,int v)
{
cc[col[x]]+=v;
for esb(x,e,b)
if(b!=f&&b!=skip) edt(b,x,v);
}
void dfs(int x,int f=0,bool kep=0)
{
for esb(x,e,b)
if(b!=f&&b!=son[x]) dfs(b,x);
if(son[x])
dfs(son[x],x,1), skip=son[x];
edt(x,f,1);
anss[x]=cc[ks[x]];
skip=0;
if(!kep) edt(x,f,-1);
}
(如果操作比较复杂的话建议看看下面的例三代码= =)
这为什么是nlogn的?因为一条重链会使所在子树大小翻一倍。
我们发现这个技巧十分好用,只要兹磁往一个集合里插入是O(1)的,删除元素到空为止是每个O(1)的(注意到这样写的话每次删除是一定会删到全空的),那么对于子树集合的询问就可以做到O(nlogn),不知道比辣鸡莫队高到哪里去了(莫队复杂度nsqrt(n),而且必须也要支持删除O(1))。
例2 Lomsat gelral(cf600E)
n个点的有根树,以1为根,每个点有一种颜色。我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多。求占领每个子树的所有颜色之和。
模板题啦。
#define SZ 666666
Edg
int n,cc[SZ],col[SZ],sz[SZ],son[SZ];
ll anss[SZ];
void gs(int x,int f=0)
{
sz[x]=1;
for esb(x,e,b)
{
if(b==f) continue;
gs(b,x); sz[x]+=sz[b];
if(sz[b]>sz[son[x]]) son[x]=b;
}
}
bool skip[SZ];
int cx=0; ll sum=0;
void edt(int x,int f,int k)
{
cc[col[x]]+=k;
if(k>0&&cc[col[x]]>=cx)
{
if(cc[col[x]]>cx)
sum=0, cx=cc[col[x]];
sum+=col[x];
}
for esb(x,e,b)
if(b!=f&&!skip[b]) edt(b,x,k);
}
void dfs(int x,int f=0,bool kep=0)
{
for esb(x,e,b)
if(b!=f&&b!=son[x]) dfs(b,x);
if(son[x])
dfs(son[x],x,1), skip[son[x]]=1;
edt(x,f,1);
anss[x]=sum;
if(son[x]) skip[son[x]]=0;
if(!kep)
edt(x,f,-1), cx=sum=0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",col+i);
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
adde(a,b);
}
gs(1); dfs(1);
for(int i=1;i<=n;i++) printf("%I64d ",anss[i]);
}
例3 Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths(CF741D)
辣鸡出题人还我rating
我们称一个字符串为doubi string当且仅当重排它的字符可以组成一个回文串。
给出一个n个点的有根树,根为1,每条边上有一个字符(只有a~v,别问我为什么),求每个点的子树中所有简单路径可以组成的doubi string中的最长长度。
doubi string显然就是只有0/1个字符出现奇数次的字符串,如果只有a~v的话考虑把每个字符当做一个二进制位,把一个点i到根的路径异或值记为s[i],那么我们就是要对于每个x在子树中找到a和b,使得s[a]^s[b]为0或2的次幂,且dep[a]+dep[b]-dep[lca]*2最大。
那么问题来了,lca如果直接当做x算出来的答案是会变大的...看起来我们需要把这个东西扩展一下,让它只统计不同子树的。这个好办,对于每棵子树先统计再更新就行了。
模板大法好!
#define SZ 1234567
Edgc
int n,sz[SZ],son[SZ],fc[SZ],dep[SZ];
void gs(int x,int f=0)
{
sz[x]=1;
for esb(x,e,b)
{
if(b==f) continue;
fc[b]=fc[x]^vc[e];
dep[b]=dep[x]+1;
gs(b,x); sz[x]+=sz[b];
if(sz[b]>sz[son[x]]) son[x]=b;
}
}
const int S='v'-'a'+1,inf=1e9;
int md[5555555],cans,skip,cdep;
void clr(int x) {md[fc[x]]=-inf;}
void upd(int x)
{
cans=max(cans,md[fc[x]]+dep[x]-cdep*2);
for(int i=0;i<S;i++)
cans=max(cans,md[fc[x]^(1<<i)]+dep[x]-cdep*2);
}
void ins(int x)
{md[fc[x]]=max(md[fc[x]],dep[x]);}
template<void(*func)(int)>
void edt(int x,int f)
{
func(x);
for esb(x,e,b)
if(b!=f&&b!=skip) edt<func>(b,x);
}
int dp[SZ];
void dfs(int x,int f=0,bool kep=0)
{
for esb(x,e,b)
if(b!=f&&b!=son[x]) dfs(b,x);
if(son[x])
dfs(son[x],x,1), skip=son[x];
cdep=dep[x];
for esb(x,e,b) if(b!=f)
dp[x]=max(dp[x],dp[b]);
for esb(x,e,b) if(b!=f&&b!=son[x])
edt<upd>(b,x), edt<ins>(b,x);
upd(x); ins(x);
dp[x]=max(dp[x],cans);
skip=0;
if(!kep) edt<clr>(x,f), cans=-inf;
}
int main()
{
for(int i=0;i<(1<<S);i++)
md[i]=-inf;
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
int x; char c[3];
scanf("%d%s",&x,c);
adde(i,x,1<<(c[0]-'a'));
}
gs(1); dfs(1);
for(int i=1;i<=n;i++)
printf("%d ",dp[i]);
}
树上启发式合并 (dsu on tree)的更多相关文章
- 神奇的树上启发式合并 (dsu on tree)
参考资料 https://www.cnblogs.com/zhoushuyu/p/9069164.html https://www.cnblogs.com/candy99/p/dsuontree.ht ...
- 【CF600E】Lomset gelral 题解(树上启发式合并)
题目链接 题目大意:给出一颗含有$n$个结点的树,每个节点有一个颜色.求树中每个子树最多的颜色的编号和. ------------------------- 树上启发式合并(dsu on tree). ...
- dsu on tree 树上启发式合并 学习笔记
近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...
- 树上启发式合并(dsu on tree)学习笔记
有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...
- dsu on tree (树上启发式合并) 详解
一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...
- 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)
(这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...
- CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 树上启发式合并(DSU ON TREE)
题目描述 一棵根为\(1\) 的树,每条边上有一个字符(\(a-v\)共\(22\)种). 一条简单路径被称为\(Dokhtar-kosh\)当且仅当路径上的字符经过重新排序后可以变成一个回文串. 求 ...
- 树上启发式合并(dsu on tree)
树上启发式合并属于暴力的优化,复杂度O(nlogn) 主要解决的问题特点在于: 1.对于树上的某些信息进行查询 2.一般问题的解决不包含对树的修改,所有答案可以离线解决 算法思路:这类问题的特点在于父 ...
- hdu6191(树上启发式合并)
hdu6191 题意 给你一棵带点权的树,每次查询 \(u\) 和 \(x\) ,求以 \(u\) 为根结点的子树上的结点与 \(x\) 异或后最大的结果. 分析 看到子树,直接上树上启发式合并,看到 ...
随机推荐
- [SQL] SQL 基础知识梳理(五) - 复杂查询
SQL 基础知识梳理(五) - 复杂查询 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5939796.html 序 这是<SQL 基础知识梳理( ...
- YYModel 源码解读(二)之YYClassInfo.h (1)
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END 为了兼容Swift 中的 ? 和 ! oc 在6.3引入了两个新的类型注释:__nullable和__non ...
- 【小型系统】简单的刷票系统(突破IP限制进行投票)
一.前言 相信大家平时肯定会收到朋友发来的链接,打开一看,哦,需要投票.投完票后弹出一个页面(恭喜您,您已经投票成功),再次点击的时候发现,啊哈,您的IP(***.***.***.***)已经投过票了 ...
- CSS制作三角形和按钮
CSS制作三角形和按钮 用上一篇博文中关于边框样式的知识点,能制作出三角形和按钮. 我先说如何制作三角形吧,相信大家在平时逛网站的时候都会看到一些导航栏中的三角形吧,比如说: 网易首页的头部菜单栏中, ...
- Base-64 字符数组或字符串的长度无效等问题解决方案
项目特殊需要,调用ActiveX三维控件进行控件某一特殊部位的截图操作,这个截图保存由ActiveX控件控制保存到本地是没问题的,现在需要将这个截图上传到服务器,多人共享,就牵扯到需要读取本地文件…… ...
- Kafka 如何读取offset topic内容 (__consumer_offsets)
众所周知,由于Zookeeper并不适合大批量的频繁写入操作,新版Kafka已推荐将consumer的位移信息保存在Kafka内部的topic中,即__consumer_offsets topic,并 ...
- sqlserver 乐观并发模式
一开始不怎么理解乐观并发模式是什么. 这种模式可以在死锁问题上使用. 在sql中 这样就是乐观并发模式. SqlServer默认开启的是悲观并发模式 例如:
- Android Time类 奇葩的设定
Android 的Time.MONTH默认是0-11表示1-12月,小白表示坑爹啊,浪费多少精力啊.
- Effective java笔记(一),创建与销毁对象
1.考虑用静态工厂方法代替构造器 类的一个实例,通常使用类的公有的构造方法获取.也可以为类提供一个公有的静态工厂方法(不是设计模式中的工厂模式)来返回类的一个实例.例如: //将boolean类型转换 ...
- 正确制作一个iframe,认识iframe
iframe作为一个网站之间交互的桥梁,受到很多站长的喜爱,但是又有不安全的因素存在,所以正确填写属性是很重要的. <iframe name="my_iframe" heig ...