这个做法和其他题解的做法是等价的。
但是那些题解没有给出严格证明,强迫症患者表示非常难受。
强行证明一波。过程中发现和莫比乌斯的联系。
题意:有m个石子围成一圈, 有n只青蛙从跳石子, 都从0号石子开始, 每次越过a[i]个石子
问所有被至少踩过的石子的序号之和
思路:
设所有踩到的石头下标集合为 P,题目求sum(P),青蛙集合为a
总共m个石子,青蛙每次跳ai。等价于m个石子,青蛙每次跳gcd(m,ai)。并且 当 gcd(m,ai)|k 时,第k个石头可以背踩到。
先对青蛙集合a做一个等价替代,ai = gcd(m,ai) (0 <=i< n),于是 P = {a集合中任意一个的倍数i | i < m} (i不能重复),求sum(p)。 变成了一个关于倍数的问题,直观感觉和莫比乌斯函数有联系。
另sta为一个01序列,表示一个a的子集,第i位1表示ai属于这个子集。
容易想到的容斥方法:
ans=∑sta=0top(−1)|sta|∗V(lcm(sta))
lcm(sta) 表示子集中所有数的最小公倍数。V(x)为 sum{x的倍数i | i < m}。
当集合较小时可以用这个公式算,复杂度是 o(2|a|) 。
到这里有一个性质还没用到,
a中元素是计算gcd(m,ai)得到的, 所以a中元素都是m的约数。所以lcm(sta) 也一定是m的约数。
m的约数个数比较少,考虑把容斥公式从枚举子集转化为 枚举 lcm(sta)的值得:
ans=∑diV(di)∗F(di)
d为m的所有约数(就是lcm的值)。 F(n)是什么?
F(n) 恰好是和 莫比乌斯函数 拥有类似性质。
个人感觉 莫比乌斯函数 是在质数集合 里的容斥,而F(d)是在一个给定集合内的容斥。
莫比乌斯函数的性质:
∑d|nμ(d)==(n==1)
F(d)函数的性质:
∑d|nF(d)==g(d)
g(d)表示d的倍数是否要被计入答案,若d的倍数都记入答案则g(d)=1
于是这个题目只要处理m的所有约数放入数组D,根据F函数的性质计算F,然后枚举 lcm 进行容斥(就是枚举D数组的元素,容斥就是套上面容斥公式)。
#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
typedef long long LL;
const int N = 10010;
int casei;
int d[N],m,n,cnt;
int F[N];
void init()
{
for(int i=0;i<n;i++)
{
int x;
scanf("%d",&x);
x = __gcd(x,m);
for(int j=0;j<cnt;j++) if(d[j]%x==0) F[j]=1;
}
F[cnt-1]=0;
for(int i = 0; i < cnt; ++i)
for(int j = 0; j < i; ++j)
if(d[i]%d[j] == 0) F[i] -= F[j];
}
inline LL V(int x)
{
LL t = (m-1)/x;
return (1+t)*t/2*x;
}
void solve()
{
MS(F,0); cnt = 0;
scanf("%d%d",&n,&m);
for(int i=1;i<=sqrt(m);i++)
{
if(m%i==0)
{
d[cnt++]=i;
if(i*i!=m) d[cnt++]=m/i;
}
}
sort(d,d+cnt);
init(); LL ans = 0;
for(int i = 0; i < cnt; i++)if(F[i]) ans += V(d[i]) * F[i];
printf("Case #%d: %lld\n",++casei,ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}