bzoj 3899 仙人掌树的同构 仙人掌 树hash

本文介绍了一种计算仙人掌图中节点置换数量的方法,通过将图转化为树并利用哈希技巧来高效地解决该问题。文章详细阐述了算法步骤,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给你一个仙人掌,然后把所有节点置换一下,求一共有多少置换满足置换后边不变。

Q:如何让代码变长?
A:将所有tab换成4个空格。
看到bz上我5k的代码我是懵逼的。。。

把一个环变成一个红点连环上的其他边。这步用一个栈维护一下dfs到当前点的所有祖先就行了。
然后变成了一棵树,那么用正常树hash就行了。

一个普通点,考虑他每种儿子的个数(hash值相同的算一种儿子),假设一种儿子有x个,那么答案乘 x!

如果是一个红点:
如果这个红点不是根,那么只需要考虑这个环翻转后和原来是否一样,如果一样那么答案2
这个只需要正着做一遍hash,反着做一遍hash,然后看一不一样。
并且这个点的hash值取正反hash值的最小值。

如果这个红点是根,那么把所有儿子hash值连起来,把串倍长,分别用这个串和他的反串匹配。答案乘匹配个数。

注意普通点和红点需要用两个hash种子。

#include <bits/stdc++.h>
using namespace std;
#define N 2100
#define M 51000
#define mod 1000000003
#define seed1 233333333
#define seed2 2147483647
#define end1 20140355
#define end2 5462617
#define ll long long
#define ull unsigned long long
int n,m;
int jc[N];
struct cactus
{
    int tot,n,f1,f2;
    int head[N],nex[M],to[M],type[N];
    int size[N],f[N],cnt[N];
    ull has[N],son[N][N],ans;
    void add(int x,int y)
    {
        tot++;
        nex[tot]=head[x];head[x]=tot;
        to[tot]=y;
    }
    void ade(int x,int y)
    {add(x,y);add(y,x);}
    void findroot(int x,int y)
    {
        size[x]=1;
        for(int i=head[x];i;i=nex[i])
            if(to[i]!=y)
            {   
                findroot(to[i],x);
                size[x]+=size[to[i]];
                f[x]=max(f[x],size[to[i]]);
            }
        f[x]=max(f[x],n-size[x]);
        if(f[x]<f[f1])f2=f1,f1=x;
        else if(f[x]<f[f2])f2=x;
    }
    void del(int x,int y)
    {
        if(to[head[x]]==y)head[x]=nex[head[x]];
        for(int i=head[x];i;i=nex[i])
            if(to[nex[i]]==y)
            {
                nex[i]=nex[nex[i]];
                break;
            }
    }
    void dfs(int x,int y)
    {
        has[x]=type[x]+1;
        for(int i=head[x];i;i=nex[i])
            if(to[i]!=y)
            {
                dfs(to[i],x);
                son[x][++cnt[x]]=has[to[i]];
            }
        if(type[x])
        {
            if(x==f1)
            {
                int sum=0;
                for(int i=1;i<=cnt[x];i++)
                    son[x][cnt[x]+i]=son[x][i];
                for(int i=1;i<=cnt[x];i++)
                {
                    int flag=0;
                    for(int j=1;j<=cnt[x];j++)
                        if(son[x][j]!=son[x][i+j-1])
                            {flag=1;break;}
                    if(!flag)sum++;
                }
                for(int i=1;i<=cnt[x];i++)
                {
                    int flag=0;
                    for(int j=1;j<=cnt[x];j++)
                        if(son[x][cnt[x]-j+1]!=son[x][i+j-1])
                            {flag=1;break;}
                    if(!flag)sum++;
                }
                ans=(ll)ans*sum%mod;
            }
            else
            {
                ull tmp=2;
                for(int i=1;i<=cnt[x];i++)
                    has[x]=has[x]*seed2+son[x][i];
                for(int i=cnt[x];i>=1;i--)
                    tmp=tmp*seed2+son[x][i];
                if(tmp==has[x])ans=ans*2%mod;
                has[x]=min(has[x],tmp);
                (has[x]*=end2)^=end2;
            }
        }
        else
        {
            sort(son[x]+1,son[x]+1+cnt[x]);
            for(int i=1;i<=cnt[x];i++)
                has[x]=has[x]*seed1+son[x][i];
            for(int i=1,j;i<=cnt[x];i++)
            {
                j=i;
                while(son[x][i]==son[x][j]&&j<=cnt[x])j++;j--;
                ans=(ll)ans*jc[j-i+1]%mod;
                i=j;
            }
            (has[x]*=end1)^=end1;
        }   
    }
    int solve()
    {
        f[0]=1<<30;ans=1;

        for(int i=1;i<=n;i++)
            if(!size[i])
            {
                f1=f2=0;
                findroot(i,0);
                if(f[f1]==f[f2])
                {
                    del(f1,f2);del(f2,f1);
                    n++;size[n]=1;
                    ade(n,f1);ade(n,f2);
                    f1=n;
                }
                dfs(f1,0);
            }
        return ans;
    }

}C;
struct init
{
    int tot,top;
    int head[N],nex[M],to[M];
    int st[N],inc[N],vis[N],fa[N],deep[N];
    void add(int x,int y)
    {
        tot++;
        nex[tot]=head[x];head[x]=tot;
        to[tot]=y;
    }
    void dfs(int x,int y)
    {
        vis[x]=1;fa[x]=y;
        st[++top]=x;deep[x]=deep[y]+1;
        for(int i=head[x];i;i=nex[i])
            if(to[i]!=y)
            {
                if(!vis[to[i]])dfs(to[i],x);
                else if(deep[to[i]]<deep[x])
                {
                    int j;
                    for(j=top;st[j]!=to[i];j--);
                    C.n++;C.type[C.n]=1;
                    for(;j<=top;j++)
                    {
                        inc[st[j]]=1;
                        C.ade(st[j],C.n);
                    }
                    if(fa[to[i]])C.ade(to[i],fa[to[i]]);
                }
            }
        top--;
        if(!inc[x]&&y)C.ade(x,y);
    }
}I;
int main()
{
    //freopen("tt.in","r",stdin);
    scanf("%d%d",&n,&m);
    jc[0]=1;
    for(int i=1;i<=n;i++)
        jc[i]=(ll)jc[i-1]*i%mod;

    for(int i=1,x,y;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        I.add(x,y);I.add(y,x);
    }
    C.n=n;
    for(int i=1;i<=n;i++)
        if(!I.vis[i])   
            I.dfs(i,0);

    printf("%d\n",C.solve());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值