• 【HDU6647】Bracket Sequences on Tree(树Hash 树上Dp)


    题目链接

    大意

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

    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]] imes H[v]~~(vin Son[u])$$其中(C)为随机构造的一个数。(主要是怕被卡)
    若对于某对(i,j),有(H[i]=H[j]),则说明以(i)为根的有根树的形态与以(j)为根是一样的。

    那么如何判断无根树呢?
    我们采用换根Dp来做,考虑(H)数组的定义式。
    那么向下转移时就有:$$H[v]=H[v]+T[Siz[u]-Siz[v]] imes(H[u]-H[v] imes T[Siz[v]])~~(vin 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]!} imes Dp[v]~~(xin 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 ",Ans); } } ```]

  • 相关阅读:
    Vsphere日记01.ESXi5.5.install
    Linux快速计算MD5和Sha1命令
    在linux虚机中装vmtools
    BT下载原理分析
    虚拟防火墙实验
    简单介绍一下vue2.0
    小白编程系列(一):编程语言选择
    html几种美丽的分割线
    利用:header匹配所有标题做目录
    markdown编辑器的小建议
  • 原文地址:https://www.cnblogs.com/ftotl/p/11810225.html
Copyright © 2020-2023  润新知