1. 程式人生 > >洛谷4895 獨釣寒江雪 (樹雜湊+dp+組合)

洛谷4895 獨釣寒江雪 (樹雜湊+dp+組合)

qwq
首先,如果是沒有要求本質不同的話,那麼還是比較簡單的一個樹形dp

我們令\(dp[i][0/1]\)表示是否\(i\)的子樹,是否選\(i\)這個點的方案數。

一個比較顯然的想法。

\(dp[i][0]=\prod (dp[p][0]+dp[p][1])\)
\(dp[i][1]=\prod dp[p][0]\)

最後直接將一號點的答案加起來就好。

qwq但是如果寫一發,就會發現第二個樣例就wei掉了
(因為題目要求本質不同)

qwq
那麼這個東西應該怎麼做呢。

因為本質不同,所以對於\(x\)形態和\(y\)形態,在同一個根的兩個形態一樣的子樹中,順序並不影響貢獻的。

所以考慮如果存在相同的應該怎麼計算答案。

為了避免出現子樹和整棵樹除去這個子樹之後的形態相似。
我們選擇用重心來當做整個樹的根。
因為重心的每個兒子都小於\(\frac{size}{2}\)
(如果存在兩個重心的話,一個比較簡便的計算方法就是用一個新點,連線兩個點)

那麼由於題目中的本質不同是旋轉同構,所以如果兩個子樹形態相同,但是處於不同的子樹,不需要一起計算(因為沒法保證其他部分還相同)

所以我們就要求的是,對於每一個子樹,求他本質相同的子樹的貢獻。

那麼同一個形態的子樹應該怎麼統計答案呢。

 int p = (dp[v[i-1]][0]+dp[v[i-1]][1])%mod;
            int pp = dp[v[i-1]][0]%mod;
            p%=mod;
            pp%=mod;
            //dp[x][0]=(dp[x][0]*C(p+now-1,p-1))%mod;
            //dp[x][1]=(dp[x][1]*C(pp+now-1,pp-1))%mod;
            dp[x][0]=(dp[x][0]*C(p+now-1,now))%mod;
            dp[x][1]=(dp[x][1]*C(pp+now-1,now))%mod;//隔板,允許為空
            //由於要求本質不同,所以我們相當於對於這now個同形態子樹,放到對應的方案數的箱子裡,允許為空,所以是隔板法、
            //因為AAB和BAA是本質相同的。   
            now=1;

因為AAB和BAA是本質相同的。
所以其實我們將相當於把一些方案數,放到不同的子樹內部。因為影響的答案只會是每個形態的個數。
這個是一個組合數,如果方案數是\(p\),然後同形態的子樹個數是\(now\)
\[C^{now}_{p+now-1}\]

實際上這個是隔板(允許為空),一個經典套路

但是為了計算方便,我們選擇選與他對稱的一個組合數進行計算,因為\(now\)的範圍是\(O(n)\)的,我們可以直接用迴圈求組合數(其實也沒別的辦法啊)

那麼現在就剩下一個問題了,就是如何判斷形態相同。
這時候就需要樹雜湊了
sro DT_Kang orz

這裡用的是亢爺的一種樹雜湊的做法。

void dfs1(int x,int fa)
{
    int num=0;
    vector<int> v;
    v.clear();
    ha[x]=1;
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (p==fa) continue;
        dfs1(p,x);
    }
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (p==fa) continue;
        v.pb(p);
        num++;
    }
    sort(v.begin(),v.end(),cmp);
    for (int i=0;i<num;i++)
    {
        ha[x]=(ha[x]+ha[v[i]]*mi[i+1])%mod;
    }
    ha[x]=ha[x]*size[x]%mod;
}

首先,我們對於每個子樹,統計他的每個兒子,按照雜湊值進行排序,從小到大乘上\(mi\),然後最後用整個的雜湊值,乘上子樹大小
(可以直接當成一個套路去記)

下面弄上整個程式碼。

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#include<set>
#define int long long
#define pb push_back

using namespace std;

inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

const int maxn = 1e6+1e2;
const int maxm = 2*maxn;
const int mod = 1e9+7;
const int qwq = 2333;

int point[maxn],nxt[maxm],to[maxm];
int cnt=1,n,m;
int ha[maxn];
int size[maxn],root,root1,root2;
int dp[maxn][2];
int mx[maxn];
int mi[maxn];
int power[maxn],inv[maxn];
int maxson,ymh=1e9;

void addedge(int x,int y)
{
  nxt[++cnt]=point[x];
  to[cnt]=y;
  point[x]=cnt;
}

int qsm(int i,int j)
{
    int ans=1;
    while (j)
    {
        if (j&1) ans=ans*i%mod;
        j>>=1;
        i=i*i%mod;
    }
    return ans;
}

int C(int n,int m)  
{
    n=n%mod;
    m=(m+mod)%mod;
    int ans=1;
    for (int i=n-m+1;i<=n;i++) ans=ans*i%mod;
    //if (inv[m]==0) cout<<m<<"()()("<<endl;
    ans=ans*inv[m]%mod;
    //int ss=ans+1;
    return ans;
}

void init(int n)
{
    n+=10;
    power[0]=1;
    inv[0]=1;
    for (int i=1;i<=n;i++) power[i]=power[i-1]*i%mod;
    inv[n]=qsm(power[n],mod-2);
    for (int i=n-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
    mi[0]=1;
    for (int i=1;i<=n;i++) mi[i]=mi[i-1]*qwq%mod;
}

void getroot(int x,int fa)
{
    size[x]=1;
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (p==fa) continue;
        getroot(p,x);
        size[x]+=size[p];
        mx[x]=max(mx[x],size[p]);
    }
    mx[x]=max(mx[x],n-size[x]);
    if (mx[x]<ymh)
    {
        ymh=mx[x];
        root1=x;
    }
    else if (mx[x]==ymh) root2=root1,root1=x;
}

void dfs(int x,int fa)
{
    size[x]=1;
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (p==fa) continue;
        dfs(p,x);
        size[x]+=size[p];
    }
}

int a[maxn];

bool cmp(int a,int b) //sro DT_Kang orz
{
    return ha[a]<ha[b];
}

void dfs1(int x,int fa)
{
    int num=0;
    vector<int> v;
    v.clear();
    ha[x]=1;
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (p==fa) continue;
        dfs1(p,x);
    }
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (p==fa) continue;
        v.pb(p);
        num++;
    }
    sort(v.begin(),v.end(),cmp);
    for (int i=0;i<num;i++)
    {
        ha[x]=(ha[x]+ha[v[i]]*mi[i+1])%mod;
    }
    ha[x]=ha[x]*size[x]%mod;
}

void solve(int x,int fa)
{
    dp[x][0]=dp[x][1]=1;
    int num=0;
    vector<int> v;
    v.clear();
    for (int i=point[x];i;i=nxt[i])
    {
        int p = to[i];
        if (p==fa) continue;
        solve(p,x);
        v.pb(p);
        num++;
    }
    sort(v.begin(),v.end(),cmp);
    int now = 1;
    v.pb(n+2);
    for (int i=1;i<=num;i++)
    {
        //cout<<v[i]<<"***"<<" ";
        if (ha[v[i]]!=ha[v[i-1]])
        {
            int p = (dp[v[i-1]][0]+dp[v[i-1]][1])%mod;
            int pp = dp[v[i-1]][0]%mod;
            p%=mod;
            pp%=mod;
            //dp[x][0]=(dp[x][0]*C(p+now-1,p-1))%mod;
            //dp[x][1]=(dp[x][1]*C(pp+now-1,pp-1))%mod;
            dp[x][0]=(dp[x][0]*C(p+now-1,now))%mod;
            dp[x][1]=(dp[x][1]*C(pp+now-1,now))%mod;//隔板,允許為空
            //由於要求本質不同,所以我們相當於對於這now個同形態子樹,放到對應的方案數的箱子裡,允許為空,所以是隔板法、
            //因為AAB和BAA是本質相同的。   
            now=1;
        }
        else
          now++;
        //cout<<v[i]<<endl;
    }
    //if (dp[x][0]==0) cout<<x<<" "<<dp[x][0]<<endl;
}

signed main()
{
   n=read();ha[n+2]=mod+2;
   init(n);
   for (int i=1;i<n;i++)
   {
      int x=read(),y=read();
      addedge(x,y);
      addedge(y,x);
   }
   getroot(1,0); //求重心 
   if (mx[root2]==ymh)
   {
     root=n+1;
     for (int i=point[root1];i;i=nxt[i])
     {
        int p = to[i];  
        if (p==root2)
        {
            to[i]=to[i^1]=n+1;
            break;
        }   
     }
     addedge(root,root1);
     addedge(root,root2);
   }
   else
   {
     root = root1;
   }
  // cout<<root<<endl;
   memset(size,0,sizeof(size));
   dfs(root,0);
   dfs1(root,0);
  // cout<<1<<endl;
   solve(root,0);
   //cout<<root<<endl;
   //cout<<root1<<" "<<root2<<endl;
   //cout<<1<<endl;
   if (root==root1)
   {
     //cout<<"****"<<endl;
     cout<<(dp[root][0]+dp[root][1])%mod;
     return 0;
   } 
   int ans=0;
   if (ha[root1]==ha[root2])
   {
        int p = dp[root2][0]%mod;
        ans=(dp[root1][1]*dp[root2][0]%mod+C(p+2-1,2))%mod;
   }
   else
   {
      //cout<<"*****************"<<endl;
      ans=(dp[root1][1]*dp[root2][0]%mod+dp[root1][0]*dp[root2][1]%mod+dp[root1][0]*dp[root2][0]%mod)%mod;
   }
   cout<<ans;
   return 0;
}