这个故事告诉我们,在做一个辣鸡出题人的比赛之前,最好先看看他发明了什么新姿势= =居然直接出了道裸题

参考链接:

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)的更多相关文章

  1. 神奇的树上启发式合并 (dsu on tree)

    参考资料 https://www.cnblogs.com/zhoushuyu/p/9069164.html https://www.cnblogs.com/candy99/p/dsuontree.ht ...

  2. 【CF600E】Lomset gelral 题解(树上启发式合并)

    题目链接 题目大意:给出一颗含有$n$个结点的树,每个节点有一个颜色.求树中每个子树最多的颜色的编号和. ------------------------- 树上启发式合并(dsu on tree). ...

  3. dsu on tree 树上启发式合并 学习笔记

    近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...

  4. 树上启发式合并(dsu on tree)学习笔记

    有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...

  5. dsu on tree (树上启发式合并) 详解

    一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...

  6. 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)

    (这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...

  7. CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 树上启发式合并(DSU ON TREE)

    题目描述 一棵根为\(1\) 的树,每条边上有一个字符(\(a-v\)共\(22\)种). 一条简单路径被称为\(Dokhtar-kosh\)当且仅当路径上的字符经过重新排序后可以变成一个回文串. 求 ...

  8. 树上启发式合并(dsu on tree)

    树上启发式合并属于暴力的优化,复杂度O(nlogn) 主要解决的问题特点在于: 1.对于树上的某些信息进行查询 2.一般问题的解决不包含对树的修改,所有答案可以离线解决 算法思路:这类问题的特点在于父 ...

  9. hdu6191(树上启发式合并)

    hdu6191 题意 给你一棵带点权的树,每次查询 \(u\) 和 \(x\) ,求以 \(u\) 为根结点的子树上的结点与 \(x\) 异或后最大的结果. 分析 看到子树,直接上树上启发式合并,看到 ...

随机推荐

  1. 读书笔记--SQL必知必会--常用MySQL(MariaDB)命令

    DBMS信息 显示DBMS的版本 select version(); 显示DBMS状态 status; 显示DBMS资源状态 show status; 显示DBMS支持的权限 show privile ...

  2. Linux 解决数量庞大wildfly容器启动与停止的脚本

    一.问题 因公司业务的发展,后台架构的变更,导致测试环境(Linux)部署与管理困难成倍增长,duang的一下,增加N倍.进入正题说问题: 问题1.  测试环境包含普通用户环境.开发者用户环境,原来只 ...

  3. NginxWeb服务器安装

    1.   安装编译工具和库文件 [root@bigdata-51cdh opt]# yum -y install make zlib zlib-devel gcc-c++ libtool  opens ...

  4. Ionic2系列——在Ionic2中使用ECharts

    在群里看到有人问怎么在Ionic2中集成ECharts来显示图表.当时答应说写个blog,简单写下步骤. 在TypeScript中如果要使用第三方库,必须要有d.ts,也就是定义文件,没有这个文件的话 ...

  5. css水平居中的各种方法

    说到水平居中,大家可能觉得很简单啊,text-align:center 就OK了. 但是,有时候会发现这样写了也没出效果.原因是什么呢?  请往下看. 水平居中:分为块级元素居中和行元素居中 行内元素 ...

  6. shiro的使用2 灵活使用shiro的密码服务模块

    shiro最闪亮的四大特征是认证,授权,加密,会话管理. 上一篇已经演示了如何使用shiro的授权模块,有了shiro这个利器,可以以统一的编码方式对用户的登入,登出,认证进行管理,相当的优雅. 为了 ...

  7. 对 Serializable和Parcelable理解

    1.首先他们两个接口都是为了实现对象的序列化,使之可以传递,所谓序列化就是将对象信息装换成可以存储的介质的过程. 2.Serializable是jdk所提供的序列化接口,该接口存在于io包下,可想用于 ...

  8. Mac入门(三)使用brew安装软件

    brew 又叫Homebrew,是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件, 只需要一个命令, 非常方便 brew类似ubuntu系统下的apt-get的功能 阅读目录 ...

  9. Struts2入门(二)——配置拦截器

    一.前言 之前便了解过,Struts 2的核心控制器是一个Filter过滤器,负责拦截所有的用户请求,当用户请求发送过来时,会去检测struts.xml是否存在这个action,如果存在,服务器便会自 ...

  10. canvas 制作flappy bird(像素小鸟)全流程

    flappy bird制作全流程: 一.前言 像素小鸟这个简单的游戏于2014年在网络上爆红,游戏上线一段时间内appleStore上的下载量一度达到5000万次,风靡一时, 近年来移动web的普及为 ...