HDU 2865 Birthday Toy

24 篇文章 1 订阅
6 篇文章 0 订阅

一、题目

题目描述

有一个 n n n个点的环,相邻两点连边,有一个中心点向所有点连边,对这个环 k k k染色,要求有边相连的点颜色不同,求旋转后本质不同的方案数。

数据范围

3 ≤ n ≤ 1 e 9 , 4 ≤ k ≤ 1 e 9 3\leq n\leq1e9,4\leq k\leq1e9 3n1e9,4k1e9

二、解法

首先中间的点只能是一种和其他点不一样的颜色,先钦定中间的颜色,其他的就变成了 k − 1 k-1 k1染色,外面套一个 burnside \text{burnside} burnside,有欧拉函数优化计算我就不多说了,考虑算不动点个数(下面 k k k默认 − 1 -1 1)。

f [ i ] f[i] f[i] i i i个循环节的方案, f [ 1 ] = 0 f[1]=0 f[1]=0(因为是一整个), f [ 2 ] = k ( k − 1 ) f[2]=k(k-1) f[2]=k(k1),考虑转移,就是在首尾之间插入一个点,可以插入一个和首尾颜色不同的点,就是 f [ i − 1 ] × ( k − 2 ) f[i-1]\times(k-2) f[i1]×(k2),但还要考虑插入点之后原来的首尾颜色可能相同,但是上面的转移却默认了不同,我们强制首尾相同,就从 f [ i − 2 ] f[i-2] f[i2]处拿方案数,然后这个点可以选 k − 1 k-1 k1种颜色,就是 f [ i − 2 ] × ( k − 1 ) f[i-2]\times(k-1) f[i2]×(k1),容易发现上面的转移很容易套上矩阵加速,优化一下就完了呗。

我又卡了很久常,可以预处理 1 e 6 1e6 1e6以内的 p h i phi phi,还有就是不能乱开 l o n g l o n g long long longlong

#include <cstdio>
#include <cstring>
const int jzm = 1e9+7;
const int M = 1000005;
#define ll long long
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,cnt,vis[M],p[M],ph[M];ll k,ans;
struct Matrix
{
    int n,m;ll a[3][3];
    Matrix() {n=m=0;memset(a,0,sizeof a);}
    Matrix operator * (const Matrix &b) const
    {
        Matrix r;
        r.n=n;r.m=b.m;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                for(int k=1;k<=b.m;k++)
                    r.a[i][k]=(r.a[i][k]+a[i][j]*b.a[j][k])%jzm;
        return r;
    }
}A,F;
void init(int n)
{
    ph[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(!vis[i]) p[++cnt]=i,ph[i]=i-1;
        for(int j=1;j<=cnt && i*p[j]<=n;j++)
        {
            vis[i*p[j]]=1;
            if(i%p[j]==0)
            {
                ph[i*p[j]]=ph[i]*p[j];
                break;
            }
            ph[i*p[j]]=ph[i]*(p[j]-1);
        }
    }
}
Matrix qkpow(Matrix a,int b)
{
    Matrix r;
    r.n=r.m=a.n;
    for(int i=1;i<=r.n;i++)
        r.a[i][i]=1;
    while(b>0)
    {
        if(b&1) r=r*a;
        a=a*a;
        b>>=1;
    }
    return r;
}
ll fspow(ll a,int b)
{
    int r=1;
    while(b>0)
    {
        if(b&1) r=r*a%jzm;
        a=a*a%jzm;
        b>>=1;
    }
    return r;
}
int phi(int x)
{
    if(x<=1e6) return ph[x];
    int r=x;
    for(int i=1;p[i]*p[i]<=x;i++)
        if(x%p[i]==0)
        {
            r=r/p[i]*(p[i]-1);
            while(x%p[i]==0) x/=p[i];
        }
    if(x>1) r=r/x*(x-1);
    return r%jzm;
}
ll work(int x)
{
    if(x==1) return 0;
    A.n=A.m=F.n=2;F.m=1;
    A.a[1][1]=k-2;A.a[1][2]=k-1;A.a[2][1]=1;
    F.a[1][1]=k*(k-1)%jzm;F.a[2][1]=0;
    F=qkpow(A,x-2)*F;
    return F.a[1][1];
}
signed main()
{
    init(1e6);
    while(~scanf("%d %lld",&n,&k))
    {
        k--;ans=0;
        for(int i=1;i*i<=n;i++)
            if(n%i==0)
            {
                ans=(ans+phi(i)*work(n/i))%jzm;
                if(i*i!=n) ans=(ans+phi(n/i)*work(i))%jzm;
            }
        ans=ans*(k+1)%jzm*fspow(n,jzm-2)%jzm;
        printf("%lld\n",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值