Luogu P4270 [USACO18FEB]Cow Gymnasts (打表找规律)

题意

传送门

题解

首先我们不竖着看奶牛而是横着看。从下往上把奶牛叫做处于第0,1,2...0,1,2...层。那么相当于第00层的不动,第11层的平移一格,第22层的平移22格,以此类推,第ii层平移ii格。假设每一层的最小正周期为xix_i,则显然有xiix_i|ixinx_i|n。因为第ii层动了ii步恰好复原,ii就一定是最小正周期的倍数;因为是nn的环排列,所以最小正周期是nn的因数。

那么就有xigcd(i,n)x_i|gcd(i,n),又因为iii1i-1互质,则gcd(i,n)gcd(i,n)gcd(i1,n)gcd(i-1,n)互质,所以xix_ixi1x_{i-1}互质。

又因为为了不让第ii层的奶牛悬空,有第ii层的位置移动后必有第i1i-1层的奶牛支撑,结合互质得到,在xix_{i}存在的情况下,xi1=1x_{i-1}=1

意思就是说除了最上面一层,下面其它层周期均为11,也就是全都占满了,每一层都是nn头奶牛。那么这个序列最多只能出现两个值,iii1i-1

我们枚举ii,最大周期是gcd(i,n)gcd(i,n),答案就是
1+i=1n1(2gcd(i,n)1)=(i=1n12gcd(i,n))(n2)\large1+\sum_{i=1}^{n-1}\left(2^{gcd(i,n)}-1\right)=\left(\sum_{i=1}^{n-1}2^{gcd(i,n)}\right)-(n-2)前面的11表示最高层是第00层,后面的表示在gcd(i,n)gcd(i,n)这一最大周期内每个值只有iii1i-1两种取法,所以是22的次幂,不是最大周期的情况也能包括在内。后面的11表示不能全是i1i-1,否则最高层就不是ii了。

暴力做是O(nlogn)O(nlogn)的,要考虑优化。

gcd(i,n)gcd(i,n)相等的一起处理,对于1x<n1\le x<ngcd(i,n)=x(1i<n)gcd(i,n)=x(1\le i<n)ii的数量为φ(n/i)φ(n/i),显然。所以答案就是(xnn12xφ(n/x))(n2)\left(\sum_{x|n}^{n-1}2^x\cdot φ(n/x)\right)-(n-2)

那么搜索出所有的因数,搜索过程中可以算出φ(n/x)φ(n/x),最后时间复杂度为O(nlogn)O(\sqrt n\cdot logn)loglog是快速幂。

CODE

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1000000007;
LL p[20], k[20], ans, a[20][40], n;
//p[i]表示第i个质因数,k[i]表示p[i]的幂
//a[i][j] = p[i]^j

int cnt;
inline LL qpow(LL a, LL b) {
    LL re=1;
    for(;b;b>>=1,a=a*a%mod)if(b&1)re=re*a%mod;
    return re;
}
void dfs(int i, LL x, LL phi) {
    if(i > cnt) {
        if(x < n)
            ans = (ans + 1ll * qpow(2, x) * phi % mod) % mod;
        return;
    }
    for(int j = 0; j <= k[i]; ++j)
        dfs(i+1, x*a[i][j], phi/a[i][j]/(j<k[i]?p[i]:1)*(j<k[i]?p[i]-1:1)); //计算phi(n/x)
}
int main () {
    scanf("%lld", &n); LL N = n;
    for(LL i = 2; i*i <= n; ++i)
        if(n % i == 0) {
            p[++cnt] = i;
            a[cnt][0] = 1;
            while(n % i == 0) {
                n /= i, ++k[cnt];
                a[cnt][k[cnt]] = a[cnt][k[cnt]-1] * i;
            }
        }
    if(n > 1) p[++cnt] = n, k[cnt] = 1, a[cnt][0] = 1, a[cnt][1] = n;
    n = N;
    dfs(1, 1, n);
    printf("%lld\n", ((ans - n + 2) % mod + mod) % mod); //记得加mod
}

事实上官方题解是这样子的 Here

posted @ 2019-12-14 14:50  _Ark  阅读(158)  评论(0编辑  收藏  举报