BZOJ3162:独钓寒江雪 (Hash判断树同构+树形DP+组合数学)

27 篇文章 0 订阅
23 篇文章 0 订阅

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3162


题目分析:一道很厉害的题,让我知道原来Hash还可以判断两棵树的形态是否相同。

这题的具体做法还是看VFK的题解吧,我表示只能orz。用简单的话概括一下题解的内容就是:

1.定义重心为树的直径的中点。如果树的直径长度为偶数,就在最中间的边上加一个虚点作为重心。
2.以重心为根变成有根树,然后对括号序列Hash+排序,判断两棵子树是否同构。
3.设某个点所有儿子的子树集合为 x(cx,nx) ∑ x ( c x , n x ) ,其中 (ci,ni) ( c i , n i ) 表示形态为 ci c i 的子树有 ni n i 棵。由于不同的形态的子树互不影响,所以问题变成了如何计算 (ci,ni) ( c i , n i ) 的对当前节点的贡献。根据组合数,值域为 [1,n] [ 1 , n ] m m 个数的组合方案为Cn+m1m
4.每个点设两个值 f,g f , g ,分别表示选或不选这个点时,其子树的方案数。然后用组合数学+DP,分类讨论一下即可维护。

最后总结一下我已知道的关于组合数的一些东西:
①n个不同的球放进m个不同的盒子里: mn m n
②n个相同的球放进m个不同的盒子里:将所有盒子和球任意打乱排成一列,并令最后一个必为盒子,然后每个盒子获得它前面一个盒子到它之间的球,最后对所有盒子从左到右编号为1~m。可知这样的方案数为 Cm1n+m1 C n + m − 1 m − 1
③值域为[1,n]的m个数的组合方案数:可以直接看成m个相同的数分到编号为1~n的n个集合里,然后化为②。VFK的pdf里给出了另一种解释:令 a1a2am a 1 ≤ a 2 … … ≤ a m ,则 a1+1<a2+2<am+m a 1 + 1 < a 2 + 2 … … < a m + m 。每种后一个序列唯一对应前一个序列。于是变为值域在 [2,n+m] [ 2 , n + m ] 中选m个不同的数,故方案数也是 Cmn+m1 C n + m − 1 m
④n个相同的球放进m个相同的盒子里:贝尔数,即第二类stirling数的前缀和。

最后,说下这题的一些注意点。首先是Hash值排序时,要将大的值排前面,因为这样深度大的点权值会变大,不容易冲突。另外,判断两棵子树是否同构,不仅要Hash值相等,还要判 f,g f , g 值是否相等,这样可以降低冲突概率。原树的重心为一条边的时候,要注意判断左右两棵子树是否同构。这题的样例挺强的,过了样例应该就能1A了QAQ。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;

const int maxn=500100;
const long long M=1000000007;
typedef long long LL;

struct edge
{
    int obj;
    edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;

LL Two[maxn];
LL nfac[maxn];

LL Hash_val[maxn];
int Len[maxn];

vector <int> Son[maxn];

LL f[maxn];
LL g[maxn];

int d[maxn];
int len;

int fa[maxn];
int dep[maxn];
int n;

void Add(int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

void Find(int node)
{
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if (son==fa[node]) continue;

        fa[son]=node;
        dep[son]=dep[node]+1;
        Find(son);
    }
}

bool Comp(int x,int y)
{
    return (Hash_val[x]>Hash_val[y]);
}

bool Judge(int x,int y)
{
    return ( Hash_val[x]==Hash_val[y] && f[x]==f[y] && g[x]==g[y] );
}

LL Get(LL n,LL m)
{
    n=n+m-1LL;
    LL ans=1;
    for (LL i=0; i<m; i++) ans=ans*(n-i)%M;
    ans=ans*nfac[m]%M;
    return ans;
}

void Work(int node,int Fa)
{
    Len[node]=0;
    f[node]=g[node]=1;
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if (son==Fa) continue;
        Son[node].push_back(son);
        Work(son,node);
    }
    if (!Son[node].size())
    {
        Len[node]=2;
        Hash_val[node]=1;
    }
    else
    {
        int k=Son[node].size();
        sort(Son[node].begin(),Son[node].begin()+k,Comp);
        Len[node]=1;
        LL &v=Hash_val[node];
        v=0;
        for (int i=0; i<Son[node].size(); i++)
        {
            int x=Son[node][i];
            v=(v*Two[ Len[x] ]%M+Hash_val[x])%M;
        }
        v=((v<<1)|1)%M;

        int head=0;
        while (head<k)
        {
            int tail=head;
            int x=Son[node][head];
            while ( tail<k && Judge(x,Son[node][tail]) ) tail++;
            int num=tail-head;
            f[node]=f[node]*Get(g[x],num)%M;
            g[node]=g[node]*Get( (f[x]+g[x])%M ,num)%M;
            head=tail;
        }
    }
}

int main()
{
    freopen("3162.in","r",stdin);
    freopen("3162.out","w",stdout);

    scanf("%d",&n);
    for (int i=1; i<=n; i++) head[i]=NULL;
    for (int i=1; i<n; i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        Add(x,y);
        Add(y,x);
    }

    dep[1]=1;
    Find(1);

    int root=1;
    for (int i=2; i<=n; i++)
        if (dep[i]>dep[root]) root=i;

    fa[root]=0;
    dep[root]=1;
    Find(root);

    int bot=1;
    for (int i=2; i<=n; i++)
        if (dep[i]>dep[bot]) bot=i;
    len=0;
    while (bot!=root) d[++len]=bot,bot=fa[bot];
    d[++len]=root;

    nfac[0]=nfac[1]=1;
    for (int i=2; i<=n; i++)
    {
        LL x=M/i,y=M%i;
        nfac[i]=M-x*nfac[y]%M;
    }
    for (int i=1; i<=n; i++) nfac[i]=nfac[i-1]*nfac[i]%M;
    Two[0]=1;
    for (int i=1; i<=n; i++) Two[i]=(Two[i-1]<<1)%M;

    if (len&1)
    {
        root=d[(len+1)>>1];
        Work(root,0);
        f[root]=(f[root]+g[root])%M;
        printf("%I64d\n",f[root]);
    }
    else
    {
        root=d[len>>1];
        bot=d[(len>>1)+1];
        Work(root,bot);
        Work(bot,root);
        LL ans;
        if ( Judge(bot,root) )
            ans=(Get( (f[bot]+g[bot])%M ,2LL)-Get(f[bot],2LL)+M)%M;
        else ans=(f[bot]*g[root]%M+g[bot]*f[root]%M+g[bot]*g[root]%M)%M;
        printf("%I64d\n",ans);
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值