【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]]\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。

#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);
    }
}
上一篇:luogu_P2014 选课


下一篇:@bzoj - 3162@ 独钓寒江雪