LG-P4270 [USACO18FEB]Cow Gymnasts P 题解

LG-P4270 [USACO18FEB]Cow Gymnasts P Solution

更好的阅读体验戳此进入

前面的几个性质的思路参考自 这篇Blog,主要是对一些我觉得难以理解的地方重写一下,以及对最后结论及实现进行更详细的证明

题面

存在 $ n $ 个平台环形排列,每个平台可以放置 $ [1, n] $ 头奶牛,每次操作时,对于第 $ i $ 层的奶牛,会顺时针移动 $ i - 1 $ 个平台并落下,求有多少种排列能使无论多少次操作每个平台的奶牛数都不改变。

$ 1 \le n \le 10^{12} $。

对于样例,有 $ (1, 1, 1, 1), (2, 2, 2, 2), (3, 3, 3, 3), (4, 4, 4, 4), (2, 3, 2, 3), (3, 2, 3, 2) $。

Solution

一道纯粹的人类智慧题。。。

然后我们这里定义的循环周期并不是一般圆周运动绕一圈的操作次数,而是一头原来在第 $ i $ 层且最高为 $ \xi $ 层的牛再次到第 $ i $ 层且最高为 $ \xi $ 层所需要的旋转次数。也就是对于一个合法的排列,两个同一高度平台之间的距离即为平台内所有牛的循环周期。

首先对于符合条件的排列,有好几个奇怪的性质:

  1. 对于第 $ i $ 层的奶牛,只能是从其他平台第 $ i $ 层的奶牛平移过来,而不可以是从更高层的奶牛掉下来。

证明:因为是符合条件的排列,我们假设序列中最高的层数为 $ m $,那么一定是前面的 $ m - 1 $ 个平台各自给这个平台贡献一头奶牛,因为如果有往前数第 $ m $ 个的贡献,那么那个平台至少要有 $ m + 1 $ 头奶牛,矛盾。如果在前 $ m - 1 $ 里有至少任意一个没有贡献,那么当前这个台子奶牛就会少至少一个,也矛盾,所以简单想一下刚才的过程,前面第一个贡献当前的第 $ 2 $ 层,第二个贡献第 $ 3 $ 层,$ \cdots $,第 $ m - 1 $ 个贡献当前的第 $ m $ 层,而第 $ m - 1 $ 个能移动 $ m - 1 $ 那么其长度也就必须至少为 $ m $,然后以此类推即可证明所有层数都是由对应层数平移过来的,得证。

  1. 对于第 $ i $ 层奶牛的循环周期(定义参考伊始)是 $ i $ 的约数。

证明:显然某一时刻第 $ i $ 层的某头奶牛一定是这层的周期为 $ T_i $ 的奶牛移动 $ k $ 次到达的,即 $ i = kT_i $,得证。

  1. 第 $ i - 1 $ 层奶牛循环周期是第 $ i $ 层奶牛循环周期的约数。

证明:考虑由性质一,第 $ i $ 层的奶牛一定还会到第 $ i $ 层,而到达的位置的第 $ i $ 层已经跑了,所以一定是有 $ i - 1 $ 层的奶牛一直垫着它走的,否则便会掉到小于 $ i $ 的位置了,所以下层的周期一定是上层的约数,即 $ T_{i - 1} \mid T_i $,得证。

  1. 任意两个平台之间的奶牛数量差不超过 $ 1 $。

证明:由性质二不难得出 $ T_i \mid i, T_{i + 1} \mid i + 1 $,由性质三不难得出 $ T_{i} \mid T_{i + 1} $,又 $ \gcd(i, i + 1) = 1 $,那么显然 $ \gcd(T_i, T_{i + 1}) = 1 $,两数互质,前者又是后者的约数,所以显然前者为 $ 1 $,所以 $ T_1 = T_2 = \cdots = T_{n - 1} = 1 $。所以对于某个层数最高为 $ i $ 的,每次位移的距离是 $ i - 1 $,周期又为 $ 1 $,那么每隔 $ i - 1 $ 个就是相同的层数,这东西不难想到就是任意两者之间差不超过 $ 1 $.

以此我们便可以得出结论:

\[ans = 2 - n + \sum_{i = 1}^{n - 1}2^{\gcd(n, i)} \]

证明:首先枚举层数最小的平台有 $ i $ 层,不难想到对于最小平台有 $ i $ 层时,循环周期一定是 $ i $ 的约数,且一共有 $ n $ 个平台,那么循环周期也一定为 $ n $ 的约数,以此不难想到,此时最大的循环周期为 $ \gcd(n, i) $,此时我们举个 $ n = 6, i = 3, \gcd = 3 $ 的例子:

Oops! The image is blocked! Please visit my own website to observe the image! 图片被墙了,请通过文章头的跳转链接访问!

此时根据我们前面的性质一定有标号相同的点值相同,那么此时 $ 2^{\gcd(n, i)} $ 就代表着枚举 $ A, B, C $ 是否为 $ i $,但是此时如果三个值均非 $ i $ 的话,最小值便不能保证为 $ i $,此时还需要对此次的贡献 $ -1 $。

然后此时我们还要考虑,为什么仅枚举是否为 $ i $ 就可以不重不漏了,考虑如钦定 $ A = i $ 时,且钦定 $ B $ 不为 $ i $,则 $ B $ 一定为 $ i + 1 $ 或 $ i - 1 $,后者则最小值会变化,不合法,所以如果不是 $ i $ 那么一定是 $ i + 1 $。所以此时如果还钦定 $ C $ 不是 $ i $,那么它要么是 $ i + 1 $ 要么是 $ i + 2 $,而 $ C $ 与下一个 $ A $ 相连,$ i + 2 $ 和 $ i $ 的差大于 $ 1 $ 了,也不合法。当然我们考虑 $ i + 2 = 5 $,由性质二,发现 $ 3 \nmid 5 $,也不合法,所以仍然为 $ i + 1 $。那么对于这个例子,显然一个数要么是 $ i $ 要么不是,不是的时候数也唯一确定,故可以如此枚举。

随便举几个例子可以发现这个结论似乎正确,那么我们现在尝试严谨一点地去证明,有结论,对于所有非 $ i $ 的点的值一定均为 $ i + 1 $。

首先考虑如果有非 $ i $ 的点,那么一定至少要有一个 $ i + 1 $,这个从我们之前的证明即可得出,因为差最大为 $ 1 $,又不可以是 $ i - 1 $。此时如果后面的值是 $ i + 2 $,那么显然此时 $ T_{i + 1} \ge 2 $,并且此时因为存在 $ i + 2 $,那么最大值一定不是 $ i + 1 $,所以从性质四的证明过程可以得到 $ T_{i + 1} = 1 $,显然矛盾。而如果所有非 $ i $ 均为 $ i + 1 $,那么 $ i + 1 $ 即为最大值,观察性质发现有且仅有最大值是可以不符合恒等于 $ 1 $ 的条件的,所以这样才是合法的。故得证。

所以换一个说法理解,我们枚举的便为此处是 $ i $ 还是 $ i + 1 $,且不能均为 $ i + 1 $,此时贡献为 $ 2^{\gcd(n, i)} - 1 $,考虑如果 $ i = n $,那么不存在 $ i + 1 $ 了,此时需要特判,不难想到最小为 $ n $ 最大为 $ n $ 那么全是 $ n $ 则为唯一的方案,如此最终答案便为:

\[\begin{aligned} ans &=\sum_{i = 1}^{n - 1}(2^{\gcd(n, i)} - 1) + 1 \\ &=2 - n + \sum_{i = 1}^{n - 1}2^{\gcd(n, i)} \\ &=2 - n - 2^n + \sum_{i = 1}^{n}2^{\gcd(n, i)} \end{aligned} \]

然后发现数据范围这个柿子肯定过不去,于是考虑优化,继续推柿子:

\[\begin{aligned} \sum_{i = 1}^{n}2^{\gcd(n, i)} &= \sum_{j = 1}^n 2^j \sum_{i = 1}^{n}[\gcd(n, i) = j] \\ &= \sum_{j = 1}^n 2^j \sum_{i = 1}^{n}[j \mid i \land j \mid n \land \gcd(\dfrac{n}{j}, \dfrac{i}{j}) = 1] \\ &= \sum_{j = 1}^n (2^j \times \varphi(\dfrac{n}{j}) \times [j \mid n]) \\ &= \sum_{j \mid n} (2^j \times \varphi(\dfrac{n}{j})) \\ &= \sum_{j \mid n} (2^{\frac{n}{j}} \times \varphi(j)) \end{aligned} \]

这个式子应该是可以继续推下去直到严格 $ O(\sqrt{n}\log n) $ 的,但是没必要,到目前这个形式就已经可以通过精细实现打倒这个复杂度了。我们发现如果无脑枚举,找因数是 $ O(\sqrt{n}) $ 的,每个因数求欧拉函数也是 $ O(\sqrt{n}) $ 的,最后会退化成 $ O(n) $,线性筛之类的更不行了,如果可以的话或许 Min25筛 之类的科技可以 $ O(n^{\frac{2}{3}}) $ 实现,刚好能过,不过 duck 不必。

显然我们可以通过分解质因数求欧拉函数,具体地,令 $ p_i $ 为质数且 $ c_i \neq 0 $,如果有:

\[n = \prod_{i = 1}^k p_i^{c^i} \]

那么:

\[\varphi(n) = n \times \prod_{i = 1}^k \dfrac{p_i - 1}{p_i} \]

然后我们答案式子枚举的是 $ n $ 的因子,显然可以通过枚举其所有质因子不重不漏地凑出来所有的因子,于是写个朴素搜索,暴力枚举每个因子有多少个,以及当前的乘积,和存在的质因子的乘积等等,如此便可以 $ O(1) $ 求出每个需要的欧拉函数,复杂度为因数个数,即 $ O(\sqrt{n}) $,然后剩下的 $ \log $ 复杂度是因为求 $ 2^j $ 的时候快速幂的复杂度,理论上用左移会更优,但是可能爆 long long。然后过程中是需要先让 $ n \div \prod p_i $ 然后再乘 $ \prod p_i - 1 $,否则会爆 long long,当然像我一样直接用 __int128_t 可以直接忽略这些问题。

至此此题解决,还是很精妙的。

Code

#define _USE_MATH_DEFINES
#include <bits/extc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;
using namespace __gnu_pbds;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MOD (ll)(1e9 + 7)

template< typename T = int >
inline T read(void);

ll N;
int tot(0);
pair < ll, int > fact[1100000];
int cur[1100000];
__int128_t ans(0);

__int128_t qpow(ll a, ll b){
    __int128_t ret(1), mul(a);
    while(b){
        if(b & 1)ret = ret * mul % MOD;
        b >>= 1;
        mul = mul * mul % MOD;
    }return ret;
}

void dfs(int p = 1, ll base = 1, __int128_t phi = 1, __int128_t div = 1){
    if(p > tot){
        phi *= base, phi /= div, phi %= MOD;
        ans = (ans + phi * qpow(2, N / base) % MOD) % MOD;
        return;
    }
    dfs(p + 1, base, phi, div);
    phi *= fact[p].first - 1;
    div *= fact[p].first;
    for(int i = 1; i <= fact[p].second; ++i)
        base *= fact[p].first, dfs(p + 1, base, phi, div);
}

int main(){
    N = read < ll >();
    ll tmp(N); ll cur(2), cnt(0);
    while(tmp > 1){
        if(cur * cur > tmp)break;
        while(tmp % cur == 0)tmp /= cur, ++cnt;
        if(cnt)fact[++tot] = {cur, cnt}, cnt = 0;
        ++cur;
    }if(tmp > 1)fact[++tot] = {tmp, 1};
    dfs();
    ans = ((((ans + 2 - N) % MOD) - qpow(2, N)) % MOD + MOD) % MOD;
    printf("%lld\n", (ll)ans);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    short flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

UPD

update-2022_11_09 初稿

posted @ 2022-12-02 21:05  Tsawke  阅读(46)  评论(0)    收藏  举报