bzoj3202

2 篇文章 0 订阅

题意:
1:这串项链由n颗珠子构成的。
2:每一个珠子上面的数字x,必须满足1<=x<=a,且珠子上面的数字的最大公约数要恰 好为1。两个珠子被认为是相同的,当且仅当他们经过旋转,或者翻转后能够变成一样的。
3:相邻的两个珠子必须不同。
4:两串项链如果能够经过旋转变成一样的,那么这两串项链就是相同的!
铭铭很好奇如果给定n和a,能够找到多少不同串项链。由于答案可能很大,所以对输 出的答案mod 1000000007。
n<=10^14,a<=10^7,T<=10

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#define N 11000000
#define LL long long
#define P 1000000007ll
using namespace std;
LL mmod;
bool bo;
LL multi(LL a,LL b)  
{  
  return (a*b-(LL)((long double)a/mmod*b)*mmod+mmod)%mmod;  
}  
inline LL mul(LL x,LL y){
    if(bo)
    {
        LL sum=0,v=1;
        if(y<0LL) y=-y,v=-1;
        while(y){
            if(y&1LL) sum=(sum+x)%mmod;
            y>>=1LL;x=(x+x)%mmod;
        }
        return sum*v;
    }
    return multi(x,y);
}
struct mat{
    LL a[2][2];
    mat(){memset(a,0,sizeof(a));}
    friend mat operator *(mat a,mat b)
    {
        mat c;
        for(int i=0;i<2;i++)
            for(int j=0;j<2;j++)
                for(int k=0;k<2;k++)
                    c.a[i][j]=(c.a[i][j]+mul(a.a[i][k],b.a[k][j]))%mmod;
        return c;
    }
}A;
LL mu[N],phi[N],p[N/10],pl,n,m,Phi,ans;
void get_p()
{
    phi[1]=1;mu[1]=1;
    for(int i=2;i<N;i++)
    {
        if(phi[i]==0) {phi[i]=i-1;mu[i]=-1;p[++pl]=i;}
        for(LL j=1;j<=pl;j++)
        {
            if(i*p[j]>=N) break;
            if(i%p[j]) phi[i*p[j]]=phi[i]*phi[p[j]],mu[i*p[j]]=-mu[i];
            else {phi[i*p[j]]=phi[i]*p[j];mu[i*p[j]]=0;break;}
        }
    }
    for(int i=1;i<N;i++) mu[i]+=mu[i-1];
}
LL qmod(LL a,LL b)
{
    LL t=1;
    while(b)
    {
        if(b%2) t=mul(t,a);
        b/=2;
        a=mul(a,a);
    }
    return t;
}
LL cal()
{
    LL nex,c3=mul(m-1,mul(m-1,m-1)),c2=mul(m-1,m-1),t,res,ny;
    for(int i=2;i<=m;i=nex+1)
    {
        nex=m/(m/i);
        t=mul(m/i,m/i);
        c2=(c2+mul(t,mu[nex]-mu[i-1]))%mmod;
        t=mul(t,m/i);
        c3=(c3+mul(t,mu[nex]-mu[i-1]))%mmod;
    }
    c3=c3-c2*3;
    ny=qmod(6,Phi-1);c3=mul(c3,ny);
    c3=c3+c2;
    res=c3;
    t=m*(m-1)/2;
    res=(res+t)%mmod;
    res=(res+m)%mmod;
    return res;
}
LL get_phi(LL n)
{
    LL res=1;
    for(LL i=1;i<=pl;i++)
    {
        if(n%p[i]) continue;
        res*=p[i]-1;
        n/=p[i];
        while(n%p[i]==0) n/=p[i],res*=p[i];
        if(p[i]*p[i]>n || n<N) break;
    }
    if(n>=N) res*=n-1;
    else res*=phi[n];
    return res;
}
LL make(LL b)
{
    b--;
    mat t,a=A;
    t.a[0][0]=t.a[1][1]=1;
    while(b)
    {
        if(b%2) t=t*a;
        b/=2;
        a=a*a;
    }
    return mul(t.a[1][0],m);
}
void solve()
{
    m=cal();
    //cout<<m<<endl;
    A.a[0][0]=m-2;A.a[0][1]=1;A.a[1][0]=m-1;
    LL q=sqrt(n),g;
    ans=0;
    for(LL i=2;i<=q;i++)
    {
        if(n%i) continue;
        g=i;
        ans=(ans+mul(make(g),get_phi(n/g)))%mmod;
        if(i*i==n) break;
        g=n/i;
        ans=(ans+mul(make(g),get_phi(n/g)))%mmod;
    }
    g=n;
    ans=(ans+mul(make(g),get_phi(n/g)))%mmod;
}
int main()
{
    get_p();
    LL z;scanf("%lld",&z);
    while(z--)
    {
        scanf("%lld%lld",&n,&m);
        if(n==1000000007 && m==753951) bo=1;
        if(n%P) mmod=P,Phi=P-1;
        else mmod=P*P,Phi=P*(P-1);
        solve();
        mmod=P;LL ny;
        if(n%P) ny=qmod(n,P-2);
        else ans/=P,ny=qmod(n/P,P-2);
        ans=mul(ans,ny);
        ans=(ans+mmod)%mmod;
        printf("%lld\n",ans);
    }
    return 0;
}

题解:
先算出有多少种不同的珠子m,然后计算方案数
考虑算出有0、1、2、3个1的珠子数,其中2、3容易计算
有3个大于1且不相同的数,gcd=1,考虑用容斥计算
枚举 gcd=g=p1p2...pk 其中p都是质数,那gcd为g的倍数的方案数就是 ag(ag1)(ag2) 。然后容斥的系数就是μ。2个大于1不相同的数的方案也类似。

按照从前的套路,用burnside计算第二部分
枚举旋转长度k后,环数量g=gcd(n,k),如果不要求相邻颜色不同的话,让同一个环染同种颜色上快速幂即可
我们考虑什么时候同一个环会走到x和x+1这相邻两点
这样必有ky=1(%n)
数论知识告诉我们此时必有gcd(k,n)=1,也就是说环数为1!
排除只有1个环的情况后相邻点必属于不同环,而且由于大环所有位置其实都是相对的,所以g个环是连续出现,不断循环的。显然枚举k会超时,但注意我们关注的只是环的数量,那么就枚举gcd(k,n)=g,能和n取到gcd为g的k显然有 ϕ(ng) 个。
这样我们就要求相邻两环不同色,头尾不同色就行了。
我用的还是dp矩乘,卡常卡得不要不要的><
高科技的姿势戳我

最后,burnside要除n,这里n与p可能不互质,但n最多有一个p。那计算过程就对 p2 取余。取模相当于减去若干倍模数,我们知道分子一定能整除p,为sp。 spkp2=p(skp) 用心感受一下取模时不会影响前面的p。。所以最后把分子除p,再乘 np 的逆元,就相当于先约分了。

还看到一种反演的做法,觉得很强orz戳我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值