poj2888 Magic Bracelet

本文介绍了一种高效计算不动点数量的方法,适用于特定类型的旋转项链问题。通过枚举因数和利用邻接矩阵求幂来计算不同旋转周期下的不动点数目。

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

因为 n 很大,不能直接枚举旋转的次数i。因为只关心 gcd(i,n) ,可以枚举 n 的因数d,不难发现有 φ(n/d) i 满足要求。
接下来考虑对于某个循环个数d=gcd(i,n)如何求出不动点个数。项链可以看成是 n/d 个长度为 d 的小圈拼成的,之所以是圈是因为每一部分顺次相接相当于一个部分首尾相接。不妨把两种珠子相邻摆放看成一条边,对m的邻接矩阵求幂就得到了一个小圈的方案数,也就是这个循环个数下的不动点数【因为每个不动点都是由 n/d 个相同的小圈拼成的】。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define LL long long
const int p=9973,maxx=100000;
int n,m,k,tot,have[maxx+10],prm[maxx+10];
struct mat
{
    int a[12][12];
    mat operator * (const mat &mm) const
    {
        mat ret;
        for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
            {
                ret.a[i][j]=0;
                for (int k=1;k<=m;k++)
                    ret.a[i][j]=(ret.a[i][j]+a[i][k]*mm.a[k][j])%p;
            }
        return ret;
    }
    mat pow(int x)
    {
        mat base,ret;
        for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
            {
                base.a[i][j]=a[i][j];
                ret.a[i][j]=(i==j);
            }
        for (;x;x>>=1,base=base*base)
            if (x&1) ret=ret*base;
        return ret;
    }
    int count()
    {
        int ret=0;
        for (int i=1;i<=m;i++)
            ret+=a[i][i];
        return ret%p;
    }
}f,g;
int pow(int base,int x)
{
    int ret=1;
    for (;x;x>>=1,base=base*base%p)
        if (x&1) ret=ret*base%p;
    return ret;
}
int phi(int x)
{
    int ret=x;
    for (int i=1;prm[i]*prm[i]<=x;i++)
        if (x%prm[i]==0)
        {
            ret=ret/prm[i]*(prm[i]-1);
            while (x%prm[i]==0) x/=prm[i];
        }
    if (x>1) ret=ret/x*(x-1);
    return ret%p;
}
void solve()
{
    int x,y,ans=0;
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=m;i++)
        for (int j=1;j<=m;j++)
            f.a[i][j]=1;
    while (k--)
    {
        scanf("%d%d",&x,&y);
        f.a[x][y]=f.a[y][x]=0;
    }
    for (int i=1;i*i<=n;i++)
        if (n%i==0)
        {
            g=f.pow(i);
            ans=(ans+phi(n/i)*g.count())%p;
            if (i*i<n)
            {
                g=f.pow(n/i);
                ans=(ans+phi(i)*g.count())%p;
            }
        }
    printf("%d\n",ans*pow(n%p,p-2)%p);
}
int main()
{
    int T;
    for (int i=2;i<=maxx;i++)
    {
        if (!have[i]) prm[++tot]=i;
        for (int j=1;j<=tot&&(LL)i*prm[j]<=maxx;j++)
        {
            have[i*prm[j]]=1;
            if (i%prm[j]==0) break;
        }
    }
    scanf("%d",&T);
    while (T--) solve();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值