题目链接

大意

给出一颗树,按下列方式生成一个括号序列。

function dfs(int cur, int parent):
print('(')
for all nxt that cur is adjacent to:
dfs(nxt, cur)
print(')')

其中可以从任一点出发,且对儿子的遍历顺序是随机的。

求本质不同的括号序列个数。

思路

前置板块:树Hash

如何判断两颗有根树是否本质一样?

我们先随机生成一个\(T\)数组(随机数被卡概率小?)

令\(Siz[u]\)表示\(u\)的子树大小,\(H[u]\)表示以\(u\)为根的有根子树的Hash值。

那么\(H[u]\)的定义式就为:$$H[u]=C+T[Siz[u]]\times H[v]~~(v\in Son[u])$$其中\(C\)为随机构造的一个数。(主要是怕被卡)

若对于某对\(i,j\),有\(H[i]=H[j]\),则说明以\(i\)为根的有根树的形态与以\(j\)为根是一样的。

那么如何判断无根树呢?

我们采用换根Dp来做,考虑\(H\)数组的定义式。

那么向下转移时就有:$$H[v]=H[v]+T[Siz[u]-Siz[v]]\times(H[u]-H[v]\times T[Siz[v]])~~(v\in Son[u])$$这样,我们就可以得到以每个点为根的有根树的Hash值了。

考虑整棵无根树,我们取以每个点为根的有根树的最大Hash值就行了,即\(Max(H[i])\).


考虑固定了起点的情况。

那么对于两颗形状不同的有根子树,其所生成的括号序列集是显然没有交集的。

则对于某个点来说,我们需要记录它本质相同的儿子子树的个数。

我们设\(Cnt[u][x]\)表示\(u\)的儿子中Hash值为\(x\)的的个数。(Map好啊)

以\(u\)为根的子树内的答案\(Dp[u]\)就为

\[Dp[u]=\frac{Son[u]!}{\prod Cnt[u][x]!}\times Dp[v]~~(x\in H[v])$$其中$Son[u]$表示$u$儿子的个数。

对于起点不固定的情况。
其实我们换根Dp就行了,细节有点多,注意一下。

最后我们统计答案时,每种Hash值只统计一次就行了。
# 代码
及其丑陋的代码。
可恶的出题人卡简单Hash。
```cpp
#include<map>
#include<ctime>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ULL unsigned long long
const int C=10007;
const int MAXT=100000;
const int MAXN=100005;
const int MOD=998244353;
const ULL LW=2305843009213693951ll;
ULL Ans;
ULL T[MAXN],H[MAXN];
int K,N,Siz[MAXN],Son[MAXN];
ULL Dp[MAXN];
ULL E[MAXN],F[MAXN];
vector<int>P[MAXN];
vector<ULL>Kind[MAXN];
map<ULL,int>Mp;
map<ULL,int>Cnt[MAXN];
ULL Mul(ULL X,ULL Y){
return (X*Y-(ULL)(X/(long double)LW*Y+1e-3)*LW+LW)%LW;
}
ULL quick_Pow(ULL x,ULL y){
if(y==0)return 1;
if(y==1)return x;
if(y%2)return (x*quick_Pow((x*x)%MOD,y/2))%MOD;
return quick_Pow((x*x)%MOD,y/2);
}
void Prepare(){
F[0]=E[0]=1;
for(int i=1;i<=MAXT;i++)
F[i]=(F[i-1]*i)%MOD;
E[MAXT]=quick_Pow(F[MAXT],MOD-2);
for(int i=MAXT-1;i>=1;i--)
E[i]=(E[i+1]*(i+1))%MOD;
}
void DFS(int u,int fa){
Siz[u]=1;H[u]=C;Dp[u]=1;
int size=P[u].size();
for(int i=0;i<size;i++){
int v=P[u][i];
if(v==fa)continue;
DFS(v,u);Son[u]++;
Dp[u]=(Dp[u]*Dp[v])%MOD;
Siz[u]+=Siz[v];
H[u]=(H[u]+Mul(H[v],T[Siz[v]]))%LW;
if(!Cnt[u][H[v]])Kind[u].push_back(H[v]);
Cnt[u][H[v]]++;
}
Dp[u]=(Dp[u]*F[Son[u]])%MOD;
size=Kind[u].size();
for(int i=0;i<size;i++){
ULL v=Kind[u][i];
Dp[u]=(Dp[u]*E[Cnt[u][v]])%MOD;
}
}
void DFS2(int u,int fa){
int size=P[u].size();
for(int i=0;i<size;i++){
int v=P[u][i];
if(v==fa)continue;
ULL Hu=(H[u]-Mul(T[Siz[v]],H[v])+LW)%LW;
ULL Dpu=Dp[u]*E[Son[u]]%MOD*F[Son[u]-1]%MOD*F[Cnt[u][H[v]]]%MOD*E[Cnt[u][H[v]]-1]%MOD*quick_Pow(Dp[v],MOD-2)%MOD;
H[v]=(H[v]+Mul(T[Siz[u]-Siz[v]],(H[u]-Mul(T[Siz[v]],H[v])+LW)))%LW;
Dp[v]=Dp[v]*E[Son[v]]%MOD*F[Son[v]+1]%MOD*F[Cnt[v][Hu]]%MOD*E[Cnt[v][Hu]+1]%MOD*Dpu%MOD;
Cnt[v][Hu]++;Son[v]++;
Siz[v]=Siz[u];
DFS2(v,u);
}
}
int main(){
srand(time(NULL));
scanf("%d",&K);
for(int i=1;i<=MAXT;i++)T[i]=(rand()*rand()*rand())%LW+1;
Prepare();
while(K--){
scanf("%d",&N);
Mp.clear();Ans=0;
for(int i=1;i<=N;i++){
H[i]=Siz[i]=0;
Dp[i]=0;Son[i]=0;
P[i].clear();
Kind[i].clear();
Cnt[i].clear();
}
for(int i=1,x,y;i<N;i++){
scanf("%d%d",&x,&y);
P[x].push_back(y);
P[y].push_back(x);
}
DFS(1,0);DFS2(1,0);
for(int i=1;i<=N;i++)
if(!Mp[H[i]]){
Ans=(Ans+Dp[i])%MOD;
Mp[H[i]]=1;
}
printf("%lld\n",Ans);
}
}
```\]

【HDU6647】Bracket Sequences on Tree(树Hash 树上Dp)的更多相关文章

  1. POJ 1741.Tree 树分治 树形dp 树上点对

    Tree Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 24258   Accepted: 8062 Description ...

  2. Bzoj3197/洛谷3296 [SDOI2013]刺客信条assassin(树的重心+树Hash+树形DP+KM)

    题面 Bzoj 洛谷 题解 (除了代码均摘自喻队的博客,可是他退役了) 首先固定一棵树,枚举另一棵树,显然另一棵树只有与这棵树同构才有可能产生贡献 如果固定的树以重心为根,那么另一棵树最多就只有重心为 ...

  3. Codeforces Round #646 (Div. 2) E. Tree Shuffling(树上dp)

    题目链接:https://codeforces.com/contest/1363/problem/E 题意 有一棵 $n$ 个结点,根为结点 $1$ 的树,每个结点有一个选取代价 $a_i$,当前 $ ...

  4. Codeforces Round #350 (Div. 2) E. Correct Bracket Sequence Editor 线段树模拟

    E. Correct Bracket Sequence Editor   Recently Polycarp started to develop a text editor that works o ...

  5. 树hash

    判断树的同构,采用树hash的方式. 树hash定义在有根树上.判断无根树同构的时候,可以比较重心为根的hash值或者比较每个点为根的hash值. h[x]表示x为根的子树的hash,g[x]表示x为 ...

  6. 【题解】彩色树 51nod 1868 虚树 树上dp

    Prelude 题目在这里:ο(=•ω<=)ρ⌒☆ Solution 蒟蒻__stdcall的第一道虚树题qaq. 首先很容易发现,这个排列是假的. 我们只需要求出每对点之间的颜色数量,然后求个 ...

  7. Codeforces E. Alyona and a tree(二分树上差分)

    题目描述: Alyona and a tree time limit per test 2 seconds memory limit per test 256 megabytes input stan ...

  8. TZOJ 4292 Count the Trees(树hash)

    描述 A binary tree is a tree data structure in which each node has at most two child nodes, usually di ...

  9. Codevs 2370 小机房的树 LCA 树上倍增

    题目描述 Description 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天,他们想爬到一个节点上去搞基,但是作为两只虫子, ...

随机推荐

  1. 官网下载mysql的方法

    mysql官网    http://www.mysql.com/ 方法一:    (1)登陆官网 (2)把页面拉到最底部,点击Downloads(GA) 下边的MySQL Community Serv ...

  2. java运算符1

    一:算术运算符(+,  -,   *,  /,  ++,  --, ) 1.+号 :可以做加法运算(加号两边为字符和数字).正数表示 字符串连接符:只要+号两边其中有一边有字符串,输出时加号就充当连接 ...

  3. MYSQL修改配置文件之后无法重启服务

    错误:修改配置文件my.ini之后无法重启服务. 原因:以记事本方式打开my.ini文件修改完之后保存.保存之后文本编码格式为操作系统默认格式utf-8.my.ini格式要是ANSI才可以正常启动服务 ...

  4. quasar框架在store中使用router跳转页面报错

    网上一通百度,终于在这篇博客中找到原因.  https://www.cnblogs.com/remly/p/12995936.html 原因是: 在router中导出了一个工厂函数, 既然是一个函数, ...

  5. 华为云 Kubernetes 管理员实训 三 课后作业

    Exercise 1 通过Deployment方式,使用redis镜像创建一个pod.通过kubectl获得redis启动日志. Deployment的名称为<hwcka-003-1-你的华为云 ...

  6. 在变压器厂中使用 ISA-95 应用程序进行调度集成

    介绍 在工业批量和连续生产/运营环境中,调度涉及将诸如罐.反应器和其他加工设备之类的资源分配给生产/运营任务.第 4 层生产/运营计划确定要制造什么产品.要制造多少产品以及何时制造.根据设备.物料.人 ...

  7. 百度地图BMap实现在行政区域内做标注

    使用环境 vue bmap.js element-ui 页面展示 前提步骤 在index中引入百度地图提供的js库 在使用的vue页面中实例化地图 <!-- 给id随便起给名字 --> & ...

  8. 18张图,详解SpringBoot解析yml全流程

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 前几天的时候,项目里有一个需求,需要一个开关控制代码中是否执行一段逻辑,于是理所当然的在yml文件中配置了一个属性作为开关,再配合nacos就可 ...

  9. 《剑指offer》面试题29. 顺时针打印矩阵

    问题描述 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字. 示例 1: 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4 ...

  10. 2月3日 体温APP开发记录

    1.阅读构建之法 现代软件工程(第三版) 2.观看Android开发视频教程最新版 Android Studio开发 3.回返地址学习,下载导入相关jar包