bzoj 3162 独钓寒江雪 - 无标号树同构 - dp - 学习笔记

121 篇文章 0 订阅
64 篇文章 0 订阅

对于无标号树计数首先要找到其重心作根,如果有多个就再建一个点连着两个点;这样能够保证一个节点的某棵子树不会和整棵树减去当前这颗子树形成的树同构,这样就转化为有根树。哈希就随便哈希即可。这个题,如果一个节点有x颗同构的树并且每一颗子树都有y中方案,那么就相当于y个变量每个变量取值>=0其和为x的方案数,组合数一波即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define uint unsigned long long int
#define lint long long
#define N 500010
#define mod 1000000007
#define gc getchar()
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
int n,sz[N],zx[3],fac[N],facinv[N];uint hv[N];
inline bool hash_cmp(int x,int y) { return hv[x]<hv[y];  }
inline int fast_pow(int x,int k,int ans=1)
{   for(;k;k>>=1,x=(lint)x*x%mod) (k&1)?ans=(lint)ans*x%mod:0;return ans; }
inline int prelude(int n)
{
    for(int i=fac[0]=1;i<=n;i++) fac[i]=(lint)fac[i-1]*i%mod;
    facinv[n]=fast_pow(fac[n],mod-2);
    for(int i=n-1;i>=0;i--) facinv[i]=(lint)facinv[i+1]*(i+1)%mod;
    return 0;
}
inline int C(int n,int m)
{
    int ans=1;
    for(int i=1;i<=m;i++)
        ans=(lint)ans*(n-i+1)%mod;
    return (lint)ans*facinv[m]%mod;
}
struct edges{
    int to,pre;
}e[N<<1];int h[N],etop,tmp[N];
inline int add_edge(int u,int v)
{   return e[++etop].pre=h[u],e[etop].to=v,h[u]=etop;   }
int dfs(int x,int fa)
{
    sz[x]=1;
    for(int i=h[x],y;i;i=e[i].pre)
        if((y=e[i].to)^fa) tmp[x]=max(tmp[x],dfs(y,x)),sz[x]+=sz[y];
    tmp[x]=max(tmp[x],n-sz[x]);
    if(tmp[x]<tmp[zx[1]]) zx[1]=x,zx[2]=0;
    else if(tmp[x]==tmp[zx[1]]) zx[2]=x;
    return sz[x];
}
int son[N],f[N][2];
int getdp(int x,int fa)
{
    for(int i=h[x],y;i;i=e[i].pre)
        if((y=e[i].to)^fa) getdp(y,x);
    int c=0;for(int i=h[x],y;i;i=e[i].pre)
        if((y=e[i].to)^fa) son[++c]=y;
    sort(son+1,son+c+1,hash_cmp),f[x][0]=f[x][1]=1;
    for(int i=1,j,cnt,y;i<=c;i=j+1)
    {
        for(cnt=1,j=i;j<c&&hv[son[j+1]]==hv[son[i]];j++,cnt++);
        y=son[i],f[x][1]=(lint)f[x][1]*C(cnt+f[y][0]-1,cnt)%mod,
        f[x][0]=(lint)f[x][0]*C(cnt+f[y][0]+f[y][1]-1,cnt)%mod;
    }
    hv[x]=998244353;
    for(int i=1;i<=c;i++) (((hv[x]*=hv[son[i]])^=hv[son[i]])+=hv[son[i]]);
    return 0;
}
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
int main()
{
    n=inn();
    for(int i=1,u,v;i<n;i++)
        u=inn(),v=inn(),add_edge(u,v),add_edge(v,u);
    tmp[zx[1]]=n+1,prelude(n+1),dfs(1,0);int rt=0;
    if(!zx[2]) rt=zx[1];
    else{
        rt=n+1;
        for(int i=h[zx[1]];i;i=e[i].pre)
            if(e[i].to==zx[2]) e[i].to=e[((i-1)^1)+1].to=rt;
        add_edge(rt,zx[1]),add_edge(rt,zx[2]);
    }
    getdp(rt,0);int ans=0;
    if(!zx[2]) ans=(f[zx[1]][0]+f[zx[1]][1])%mod;
    else{
        int x=zx[1],y=zx[2];if(hv[x]==hv[y]) ans=(((lint)f[x][0]*f[y][1]%mod+C(f[x][0]+1,2))%mod)%mod;
        else ans=(((lint)f[x][0]*f[y][1]%mod+(lint)f[x][1]*f[y][0]%mod)+(lint)f[x][0]*f[y][0]%mod)%mod;
    }
    return !printf("%d\n",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值