[数学专题大汇总][SDOI2013]项链

前言

这题是一道质量非常好的题,涉及到许多的数学算法和思想,而又毫无违和感,没有给人强行多合一的感觉,是一道温故知新的好题。

题目大意

一条首尾相连的项链由 n 个珠子组成,每个珠子有 3 个面,每个面都有一个数字,这三个数字都是小于等于 a 的正整数,且三个数的最大公约数为 1
两个珠子相同当且仅当它们经过翻转和旋转之后相同。项链上任意两个相邻位置的珠子不能相同。
两条项链相同当且仅当它们经过旋转后一样。
给定 n a ,求不同项链数模 1000000007 的值。
每个测试点有 T 组数据。

1n1014,1a107,1T10
当然对于最大数据点,为了防止卡常数,只有一组数据。

题目分析

分而治之

显然这个问题可以分成两个子问题:
有多少种不同的合法的珠子。
有多少种不同的合法的项链。

Part 1

考虑如何求不同的合法的珠子。
简化问题,每一种合法的珠子可以看成一个三元组 (x,y,z) ,满足 0<xyza gcd(x,y,z)=1
除去三个元素互相之间的大小限制,问题显然会简单很多,但是这里有大小的限制,考虑如何除去大小限制(当然 (0,a] 的限制还是要有的,这里和下面一般指的都是元素之间的)。
考虑无序合法三元组,设其为个数 ans3 ,如果三元组之间没有相同元素,那么每种本质相同的三元组也就都出现了 6 次,我们除去就好,但实际上会出现有两个甚至三个元素相同,这样出现次数是不够 6 次的,我们要想办法补齐。无序合法二元组,设其个数为 ans2 ,它们显然每个都仅出现了 3 次。别忘了考虑三个数相等的三元组,合法的显然只有 (1,1,1) (别忘了 gcd 限制),它只出现了 1 次,但是前面在二元组时我们又补了三次,所以只要再补上两次即可。
因此,不同珠子个数为 16(ans3+ans2×3+2)
那我们怎样去求 ans3 以及 ans2 呢。

莫比乌斯反演

显然这是莫比乌斯反演的经典问题,求某个范围之内 gcd 为定值数对个数,我们之前只讨论了两个数的情况,三个数的其实类似。

ans2=d=1aai2μ(i)ans3=d=1aai3μ(i)

由于这里我们只用求出 gcd=1 的数对个数,且 a 的大小支持一次枚举,所以我们可以不使用分块,直接算上面的式子即可。

至此,我们已经求出不同的合法珠子个数,设其为 m
在下面的部分中,我们将其不同种类的珠子看做不同的颜色,显然这题变为给首尾相连的项链染色,相邻两个珠子颜色不同,两个项链相同当且仅当项链旋转后相同。求不同项链个数。

Part 2

Burnside引理

显然,这个问题现在变成了等价类计数问题,这里的等价关系是旋转。
根据 Burnside 引理,答案显然为

ni=1C(i)n

其中 C(i) 表示经过旋转 i 位的操作后,不动点的个数。
注意这里不动点还有合法的限制,也就是相邻两个点颜色不能相同。
我们考虑将置换拆成若干个循环的乘积。显然一个循环的长度为
lcm(n,i)i=ngcd(n,i)

因此循环就有 gcd(n,i) 个。
现在我们证明所有循环的是相邻的。
两个点 u v 在同一个循环内当且仅当
u+piu+pikn+piv(modn)=kn+v=vu

上式中 k p 为未知数根据裴蜀定理,这个方程有解当且仅当 gcd(n,i)|(vu) ,即 (uv)0(modgcd(n,i)) ,因此有 uv gcd(n,i)1 总本质不同的取值,能使 u v 不在同一个循环中,而这些取值是相邻的,由此我们也可以看出循环有 gcd(n,i) 个。
根据 Polya 定理,我们知道同一个循环内的所有点颜色都相同,并且相邻两个循环代表一系列项链中相邻的点,所以不能染同样颜色。
此时我们已经可以将循环看成点。问题变成有 gcd(n,i) 个珠子,首尾相连,相邻两个珠子颜色不同,求合法染色方案 f(gcd(n,i))
我们先不考虑如何求解 f ,我们考虑得到 f 之后如何快速求解答案。

欧拉函数

现在答案变成

i=1nf(gcd(n,i))

由于 n 很大,直接枚举会炸,所以我们考虑只枚举 n 的约数 d ,显然这代表所有的 gcd(n,i)
那么在 n 以内有多少个数 i 满足 gcd(n,i)=d 呢?
如果满足 gcd(n,i)=d 那么显然有 gcd(nd,id)=1 ,这个就可以使用欧拉函数 φ 。因此,答案为
d|nf(d)φ(nd)

如果我们枚举约数,除去求 f 时间复杂度就是 O(n) 的,因为 n 的约数最多有 n 个(证明自己思考,参见莫比乌斯反演里面分块时间复杂度的证明)。这个欧拉函数可以在枚举约数过程中递推(使用 dfs 枚举约数),或者枚举后分解质因数求(直接从 1 n 枚举约数,这样貌似很慢)。

矩阵乘法

那么问题来了,如何高效求解 f
我们分析递推关系,可以列出 f 的一条递推式 f(1)=0 f(2)=m(m1) f(n)=f(n1)(m2)+f(n2)(m1)  (n>2) f(n1) 表示 n1 个珠子两边颜色不同,我们插入一个合法的; f(n2) 表示 n2 个珠子,两边颜色不同,然后我们先插入一个,使得它颜色与第一颗珠子相同,再插入一个与第一个颜色不同的去维持合法)。
然后我们可以使用矩阵乘法加快速幂来计算 f
时间复杂度 O(nlogn2)
然而,这里的时间复杂度忽略了矩阵乘法本身的复杂度常数 8 。在实测中,这个算法跑得非常的慢。如果想不要被卡常数,我们得考虑用一种不使用矩阵乘法的算法。

求通项公式

这里需要一定的数学推导技巧。
f(n)=f(n1)(m2)+f(n2)(m1)
这是一类形如 f(n)=pf(n1)+qf(n2) 的递推公式,通常将其变形为 f(n)+λf(n1)=(p+λ)(f(n1)+λf(n2)) 的形式,得到关于 f(n)+λf(n1) 的等差数列。
我们对其变形

f(n)f(n)+λf(n1)f(n)m1λ=1=f(n1)(m2)+f(n2)(m1)=[(m2)+λ][f(n1)+λf(n2)]=f(n1)(m2)+λ2f(n2)+λ(m2)f(n2)=λ(λ+m2)λ=m+1

代入前面那个方便一些,得:
f(n)+f(n1)f(n)+f(n1)=((m2)+1)(f(n1)+f(n2))=(m1)(f(n1)+f(n2))

S(n)=f(n)+f(n1) ,则 S(n)=(m1)S(n1)
显然 S(2)=f(2)f(1)=m(m1)
S(n)=(m1)n1m ,因此 f(n)=f(n1)+(m1)n1m
这还是一个递推式,如何通项?考虑将其化为 f(n)+an+k=b(f(n1)+an+k1) 的形式,使用等比数列得解。
f(n)f(n)+TTm1TT+T(m1)m1T=f(n1)+(m1)n1m=(f(n1)+Tm1)=(m1)n1m=(m1)n1m=(m1)n

因此 f(n)(m1)n=(f(n1)(m1)n1) 。当 n=1 时有 f(n)(m1)n=m+1 。因此 f(n)(m1)n=(m+1)(1)n1 ,即 f(n)=(m1)(1)n+(m1)n 。使用快速幂直接得出。
时间复杂度 O(nlogn2)

你以为这样就能过了了吗?

想想我们忽略了什么问题?
在使用 Burnside 引理的时候我们将答案除了 n 。你可能说模数是质数,我们直接快速幂求逆元即可。但是别忘了 n1014 ,它可能会是模数 p 的倍数。
那我们咋办?在计算过程中我们先对 p2 取模。在除法时,如果 n 不是 p 的倍数,那我们直接弄就行了;如果是,那我们先对答案除 p (肯定除得尽,想想为什么),然后再乘上 np 在模 p 意义下的逆元,模 p 即可。
代码实现中我取模貌似打错了,不过没有这种数据。请读者自行更正。

代码实现

代码打得有点丑, 3000ms 时限, 2900+ms 强行碾过去了。

#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <cctype>
#include <cmath>
//#include <ctime>

using namespace std;

typedef long long LL;

const int A=10000050;
const int p=1000000007;

LL P;

LL mult(LL x,LL y)
{
    LL ret=0;
    while (y)
    {
        if (y&1)
            (ret+=x)%=P;
        (x<<=1)%=P;
        y>>=1;
    }
    return ret;
}

void extended_gcd(LL &a,LL &b)
{
    if (!b)
    {
        a=1;
        b=0;
        return;
    }
    LL a0=b,b0=a%b;
    extended_gcd(a0,b0);
    b=a0-(a/b)*b0;
    a=b0;
}

LL inverse_element(LL a,LL p)
{
    LL x=a,y=p;
    extended_gcd(x,y);
    return ((x+p)%p+p)%p;
}

int mu[A],pri[A];
bool mark[A];
LL K,n,ans;
int a,T;

void pre()
{
    mark[1]=true;
    mu[1]=1;
    //phi[1]=1;
    for (int i=2;i<=A;i++)
    {
        if (!mark[i])
        {
            mu[i]=-1;
            //phi[i]=i-1;
            pri[++pri[0]]=i;
        }
        for (int j=1;j<=pri[0];j++)
        {
            if (1ll*i*pri[j]>A)
                break;
            mark[i*pri[j]]=true;
            if (!(i%pri[j]))
            {
                mu[i*pri[j]]=0;
                //phi[i*pri[j]]=phi[i]*pri[j];
                break;
            }
            mu[i*pri[j]]=-mu[i];
            //phi[i*pri[j]]=phi[i]*(pri[j]-1);
        }
    }
}

LL quick_power(LL x,LL y)
{
    LL ret=1;
    while (y)
    {
        if (y&1)
            ret=mult(ret,x);
        x=mult(x,x);
        y>>=1;
    }
    return ret;
}

LL f(LL d)
{
    LL fn=d&1?-K+1:K-1;
    fn=(fn%P+P)%P;
    (fn+=quick_power((K-1+P)%P,d))%=P;
    return fn;
}

struct D
{
    LL pri;
    int idx;
}d[A];
int cnt;

void dec(LL x)
{
    cnt=0;
    int cur=1,upp=sqrt(x);
    while (pri[cur]<=upp&&x!=1)
    {
        while (pri[cur]<=upp&&(x%pri[cur]))
            cur++;
        if (pri[cur]>upp)
            break;
        d[++cnt].pri=pri[cur];
        d[cnt].idx=0;
        while (!(x%pri[cur]))
        {
            d[cnt].idx++;
            x/=pri[cur];
        }
    }
    if (x!=1)
    {
        d[++cnt].pri=x;
        d[cnt].idx=1;
    }
}

void dfs(int x,LL now,LL phi)
{
    if (x==cnt+1)
    {
        (ans+=mult(f(n/now),phi))%=P;
        return;
    }
    dfs(x+1,now,phi);
    phi*=(d[x].pri-1);
    for (int i=1;i<=d[x].idx;i++)
    {
        now*=d[x].pri;
        dfs(x+1,now,phi);
        if (i!=d[x].idx)
            phi*=d[x].pri;
    }
}

void solve()
{
    ans=0;
    dec(n);
    dfs(1,1,1);
    P=p;
    ans=mult(ans,inverse_element(n,1ll*p*p));
}

int main()
{
    pre();
    freopen("necklace.in","r",stdin);
    freopen("necklace.out","w",stdout);
    scanf("%d",&T);
    //int t1=time(0),t2,t3;
    while (T--)
    {
        P=1ll*p*p;
        scanf("%lld%d",&n,&a);
        LL ans2=0,ans3=0;
        for (int i=1;i<=a;i++)
            (((ans2+=1ll*mu[i]*mult(a/i,a/i))%=P)+=P)%=P,(((ans3+=1ll*mu[i]*mult(mult(a/i,a/i),a/i))%=P)+=P)%=P;
        K=mult((ans3+ans2*3+2)%P,inverse_element(6,P));
        //t2=time(0);
        solve();
        //t3=time(0);
        printf("%lld\n",ans);
    }
    //printf("%d %d %d\n",t1,t2,t3);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

你可能感兴趣的:(数论,OI,群论)