[HDU5822]color

题目大意

给你一棵 n 个点的基环树,用m种颜色将这棵树染色,问有多少种本质不同的方案。
答案对 109+7 取模。

给定的基环树是有向的,即环上点连成一个正常的简单环,非环上的点按拓扑序从没有入度的开始向里面连边。

A B两种染色基环树本质不同的p定义是,不存在双射 M 满足:
 x,A(x)=B(M(x))
 directed edge (x,y)E,(M(x),M(y))E

3n105,1m109


题目分析

这是一道集合很多套路的好题,虽然每一步都基本上是套路,但是这么多套路一步步串起来,还是让人有点不知道如何下手。
考虑如果题目给的是一棵有根树怎么做。
显然,对于有根树而言,方案出现本质相同的话一定是一个点的同构子树记重了。
计算点 x 的答案时我们先把它所有的儿子按子树哈希值归类,不同类的显然可以乘法原理。
对于同类的,假设有tot个,方案是 x 种。那么我们相当于要个tot个位置分若干个段:

a=1x(xa)(tot1a1)=(tot+x1tot)

这个 x 我只能够算出模P=109+7意义下的,这可咋整啊?
Lucas定理!
(tot+x1tot)=((tot+x1) mod Ptot)tot+x1PtotP

注意到 totP=0 ,我们只需要计算 ((tot+x1) mod Ptot)
我们可以 O(tot) 地算出这个组合数,这样总时间复杂度是 O(n) 的。
现在我们已经可以计算出有根树的染色方案了,考虑如何把它弄到环上。
显然题目模型可以转化为一个长度为 cnt 的简单环,每个点有种类 type (即有根树的哈希值)以及染色方案 x ,求本质不同的染色方案数。
我们使用Burnside引理来做这题。有向环的同构只需要考虑旋转,而这里并不是所有的旋转操作都是合法的。我们的旋转操作必须要满足转了之后对应点的哈希值没有变化。
假如我们是顺时针旋转i个位置,相当于要求 gcd(i,cnt) 必须是这个环哈希序列的一个周期(在这里我们“周期”的定义和字符串的period类似,但是必须是串长的约数)。
显然我们可以通过KMP求出这个最短的周期,然后所有周期都是这个最短周期的倍数。
假如旋转 i 步是合法的话,它的不动点个数就是
j=1gcd(i,n)f(cir(j))

我们预处理一下这个环前缀积,就可以很快计算出来。
然后我们的问题就解决了,时间复杂度 O(nlogn)


代码实现

#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cctype>
#include <vector>
#include <queue>

using namespace std;

typedef vector<int>::iterator itr;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int MOD=1000000007;
const int P=67;
const int N=100050;

int trs[N],deg[N],mult[N],fact[N],invf[N],h[N],size[N],POW[N],list[N],nxt[N],f[N];
int T,n,m,cnt,rt,ans,period;
vector<int> son[N];
queue<int> q;

int quick_power(int x,int y)
{
    int ret=1;
    for (;y;y>>=1,x=1ll*x*x%MOD) if (y&1) ret=1ll*ret*x%MOD;
    return ret;
}

int C(int n,int m)
{
    assert(n>=m);
    int ret=invf[m];
    for (int i=n-m+1;i<=n;++i) ret=1ll*ret*i%MOD;
    return ret;
}

bool cmp(int x,int y){return h[x]<h[y];}

int gcd(int x,int y){return y?gcd(y,x%y):x;}

void pre()
{
    fact[0]=1;
    for (int i=1;i<=n;++i) fact[i]=1ll*fact[i-1]*i%MOD;
    invf[n]=quick_power(fact[n],MOD-2);
    for (int i=n;i>=1;--i) invf[i-1]=1ll*invf[i]*i%MOD;
    POW[0]=1;
    for (int i=1;i<=n;++i) POW[i]=1ll*POW[i-1]*P%MOD;
}

void dp()
{
    for (int x=1;x<=n;++x) ++deg[trs[x]],size[x]=h[x]=0;
    for (int x=1;x<=n;++x) if (!deg[x]) q.push(x);
    for (int x,y;!q.empty();)
    {
        ++size[x=q.front()],q.pop(),sort(son[x].begin(),son[x].end(),cmp);
        int siz=0;
        f[x]=m;
        for (itr it=son[x].begin(),it_;it!=son[x].end();)
        {
            int tot=0;
            for (it_=it;it!=son[x].end()&&h[*it]==h[*it_];++it) ++tot,y=*it,(h[x]+=1ll*h[y]*POW[siz<<1]%MOD)%=MOD,siz+=size[y];
            f[x]=1ll*f[x]*C((f[*it_]+tot-1)%MOD,tot)%MOD;
        }
        h[x]=(1ll*h[x]*P+1+2ll*POW[size[x]<<1|1])%MOD;
        son[y=trs[x]].push_back(x),size[y]+=size[x];
        if (!--deg[y]) q.push(y);
    }
    for (int x=1,y;x<=n;++x)
        if (deg[x])
        {
            ++size[x],sort(son[x].begin(),son[x].end(),cmp);
            int siz=0;
            f[x]=m;
            for (itr it=son[x].begin(),it_;it!=son[x].end();)
            {
                int tot=0;
                for (it_=it;it!=son[x].end()&&h[*it]==h[*it_];++it) ++tot,y=*it,(h[x]+=1ll*h[y]*POW[siz<<1]%MOD)%=MOD,siz+=size[y];
                f[x]=1ll*f[x]*C((f[*it_]+tot-1)%MOD,tot)%MOD;
            }
            h[x]=(1ll*h[x]*P+1+2ll*POW[size[x]<<1|1])%MOD;
        }
    rt=0;
    for (int x=1;x<=n&&!rt;++x) if (deg[x]) rt=x;
    cnt=0;
    for (int x=rt;x!=rt||deg[rt];deg[x]=0,x=trs[x]) list[cnt++]=x;
    int ptr=0;
    for (int i=1;i<cnt;++i)
    {
        for (;ptr&&h[list[i]]!=h[list[ptr]];ptr=nxt[ptr-1]);
        nxt[i]=ptr+=h[list[i]]==h[list[ptr]];
    }
    for (int i=0;i<cnt;++i) mult[i]=1ll*(i?mult[i-1]:1)*f[list[i]]%MOD;
    if (cnt-nxt[cnt-1]<=nxt[cnt-1]) period=cnt-nxt[cnt-1];
    else period=cnt;
    int tot=0;ans=0;
    for (int i=0,l;i<cnt;++i)
        if (!((l=gcd(i,cnt))%period)) ++tot,(ans+=mult[l-1])%=MOD;
    ans=1ll*ans*quick_power(tot,MOD-2)%MOD;
}

void clear(){for (int x=1;x<=n;++x) son[x].clear();}

int main()
{
    freopen("color.in","r",stdin),freopen("color.out","w",stdout);
    for (T=read();T--;printf("%d\n",ans))
    {
        clear(),n=read(),m=read();
        for (int i=1;i<=n;++i) trs[i]=read();
        pre(),dp();
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值