Step1 Problem:
m 个石柱围成一个圈,有 n 只青蛙从 0 开始跳,第 i 只青蛙当前位置是 pos 下一次能跳到 pos+a[i] 的石柱上,问所有柱子被踩到一次的序列和。
数据范围:
1<=n<=1e4, 1<=m<=1e9, 1<=a[i]<=1e9.
Step2 Ideas:
前置技能:
(a*t)%k = y, t 为非负整数,求 y.
a*t = k*t1 + y; a*t - k*t1 = y, a*t - k*t1 = gcd(a, k)
y = gcd(a, k) 的倍数。
有了上述前置技能:
我们知道每只青蛙可能跳那些柱子,2^n 直接容斥,显然会 TLE,因为都会和 m 求 gcd,所以有很多重复计算了。
由于 gcd(a[i], m) 是 m 的因数,我们可以对因数进行容斥。
2^因数个数 显然还是会 TLE,所以我们得优雅的容斥
假设 x = gcd(a[i], m), y = gcd(a[i+1], m), z = gcd(a[i+2], m) 同时 x, y, z 不相等。
如果 y 是 x 的倍数,x 的倍数的柱子都走过后,y 就不需要走了。
如果 gcd(x, y) = 1, z 是 x 的倍数 同时 也是 y 的倍数,x 和 y 走完,多走了 z 的倍数,所以还需要减去 z 的倍数。
这样我们就可以从小到大枚举因数,对当前因数的倍数进
行减操作,就可以 O(因数个数^2) 的优雅容斥了
Step3 Code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int fact[200], vis[200], ed[200];
int main()
{
int T, n, m, num;
scanf("%d", &T);
for(int Case = 1; Case <= T; Case++)
{
scanf("%d %d", &n, &m);
int cnt = 0;
for(int i = 1; i*i <= m; i++)
{
if(m%i == 0) {
fact[cnt++] = i;
if(i*i != m) {
fact[cnt++] = m/i;
}
}
}
sort(fact, fact+cnt);
memset(ed, 0, sizeof(ed));
memset(vis, 0, sizeof(vis));
for(int i = 0; i < n; i++)
{
scanf("%d", &num);
int a = __gcd(num, m);
for(int j = 0; j < cnt; j++)
{
if(fact[j]%a == 0) {
vis[j] = 1;
}
}
}
cnt--;
ll ans = 0;
for(int i = 0; i < cnt; i++)
{
if(vis[i] != ed[i]) {
int t = (m-1)/fact[i];
//ans += (ll)(fact[i]+(t*fact[i]))/2 * t * (vis[i]-ed[i]);
ans += (ll)t*(1+t)/2 * fact[i] * (vis[i]-ed[i]);
for(int j = i+1; j < cnt; j++)
{
if(fact[j]%fact[i] == 0) {
ed[j] += (vis[i]-ed[i]);
}
}
}
}
printf("Case #%d: %lld\n", Case, ans);
}
return 0;
}