题意是给出n个m的约数,问[0,m-1]中至少被其中一个约数整除的整数和。(n<=10000,m<=1000000000)
直接容斥的话,是2^n再拖个log的复杂度,加上当前的数大于m时直接跳出了剪枝,或许会小一点。
但是有一个很重要的性质:我们容斥中所有计算过贡献的数,都是m的因数。换言之,我们计算贡献的数的个数是及其有限的,故可以计算出所有因数贡献的系数。
我们给所有因数赋予互不重叠的“贡献”,定义为所有能被其整除但不能被其他大于它的因数整除的数的和。这种贡献是我们用来表示答案的组成的,但是不可以直接计算。
于是一开始,我们把所有是这n个约数的倍数的贡献设为1。事实上,容斥就体现在这里。我们这里是设为1,而不是加1。即对于一个同时是多个因数的倍数的数,我们只计算一次贡献。
然而,我们在实际计算中,一个数的贡献在它的所有因数中都计算过了。设因数i的实际系数是ri,初始定义的系数是vi,那么,我们有
时间复杂度O(√m+n*d(m)+d(m)^2)。(好像非常玄学)
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 10010; 4 typedef long long ll; 5 ll ans; 6 int a[N],n,m,p,fac[N],num[N],cnt,tmp; 7 int main() { 8 int t; 9 scanf("%d",&t); 10 for (int ca = 1 ; ca <= t ; ++ ca) { 11 memset(num,0,sizeof num); 12 scanf("%d%d",&n,&m); 13 cnt = 0; 14 ans = 0; 15 for (int i = 1 ; i * i <= m ; ++ i) if (m%i == 0) { 16 fac[++ cnt] = i; 17 if (i * i != m) fac[++ cnt] = m/i; 18 } 19 sort(fac+1,fac+cnt+1); 20 for (int i = 1 ; i <= n ; ++ i) { 21 scanf("%d",&a[i]); 22 a[i] = __gcd(a[i],m); 23 for (int j = 1 ; j <= cnt ; ++ j) 24 if (fac[j] % a[i] == 0) num[j] = 1; 25 } 26 for (int i = 1 ; i <= cnt ; ++ i) if (num[i]) { 27 tmp = m / fac[i]; 28 ans += 1ll * tmp * (tmp - 1) / 2 * fac[i] * num[i]; 29 for (int j = i + 1 ; j <= cnt ; ++ j) 30 if (fac[j] % fac[i] == 0) num[j] -= num[i]; 31 } 32 printf("Case #%d: %lld\n",ca,ans); 33 } 34 return 0; 35 }
小结:在容斥对象数量少的时候,存下其中的每一个贡献的系数是可取的优化方向。