CodeForces 839D Winter is here 容斥原理

原题链接:http://codeforces.com/contest/839/problem/d


题目大意

对于一个数列 a1,a2,...,an a 1 , a 2 , . . . , a n ,若 gcd(a1,a2,...,an)2 g c d ( a 1 , a 2 , . . . , a n ) ⩾ 2 ,那么令这个数列的价值为 n×gcd(a1,a2,...,an) n × g c d ( a 1 , a 2 , . . . , a n )
给定一个数列,求它的所有子序列的价值和(长度可以为 1 1 ,且可以不连续)。

解题思路

大致思路取自http://www.cnblogs.com/ECJTUACM-873284962/p/7352299.html,这里给上这种思路的详尽解释

发现若根据数列中的元素做,会十分困难。我们不妨从 gcd g c d 下手。
现在,我们考虑 gcd=i g c d = i 的情况。我们记 x x 为序列中能被 i i 整除的数的个数。然后我们尝试表示这种情况下的价值。
我们发现, gcd g c d i i 的倍数的数列的长度总和很容易被维护:

g[i]=j=1xj×(xj)=x×2x1[1] g [ i ] = ∑ j = 1 x j × ( x j ) = x × 2 x − 1 [ 1 ]

那么我们通过容斥原理,可知所求的 gcd=i g c d = i 的数列的长度总和 f[i] f [ i ]
f[i]=g[i]g[i×2]+g[i×3]...=f[i]f[i×2]f[i×3]... f [ i ] = g [ i ] − g [ i × 2 ] + g [ i × 3 ] − . . . = f [ i ] − f [ i × 2 ] − f [ i × 3 ] − . . .

到此,我们可以知道 gcd=i g c d = i 的数列对答案的贡献为
f[i]×i f [ i ] × i

所以我们将原数列 ai a i 以桶排的方式放进一个个桶内,然后只需枚举 i i 。同时可以知道求 x x f[i] f [ i ] 的时间是 log l o g 级的。
至此我们得到了一个 O(max(ai)log2(max(ai)) O ( m a x ( a i ) l o g 2 ( m a x ( a i ) ) 的做法。为了方便容斥,程序中倒着枚举 i i

以下给出 [1] [ 1 ] 式第二个等号的证明。


S1=i=0n(ni)×i S 1 = ∑ i = 0 n ( n i ) × i

S2=i=0n(ni)×(ni) S 2 = ∑ i = 0 n ( n i ) × ( n − i )

那么
S1+S2=n×i=0n(ni)=n×2n S 1 + S 2 = n × ∑ i = 0 n ( n i ) = n × 2 n

同时
(ni)=(nni) ( n i ) = ( n n − i )

所以
S2=i=0n(nni)×(ni)=i=0n(ni)×i=S1 S 2 = ∑ i = 0 n ( n n − i ) × ( n − i ) = ∑ i = 0 n ( n i ) × i = S 1

所以
S1=12×n×2n=n×2n1 S 1 = 1 2 × n × 2 n = n × 2 n − 1

其中 S1 S 1 的首项为 0 0 ,得证

参考程序

这种年头难得有程序如此短小精悍却又蕴含一定思维层次的题了。

#include 
#define LL long long
const LL N = 1000010, mod = 1000000007;
LL a[N], n, ans, f[N], P[N], x;
int main() {
    P[0] = 1;
    for(LL i = 1; i < N; i++)
        P[i] = P[i - 1] * 2 % mod;//预处理2的幂次
    scanf("%I64d", &n);
    for(LL i = 1; i <= n; i++)
        scanf("%I64d", &x), a[x]++;//将原数列放进桶里
    for(LL i = N - 1; i > 1; i--) {//倒着枚举i
        x = 0;
        for(LL j = i; j < N; j += i) x += a[j];//求x
        if(x == 0) continue;
        f[i] = x % mod * P[x - 1] % mod;
        for(LL j = i + i; j < N; j += i)
            f[i] = ((f[i] - f[j]) % mod + mod) % mod;//求f[i]
        ans = (ans + f[i] * i % mod) % mod;
    }
    printf("%I64d\n", ans);
    return 0;
}

你可能感兴趣的:(组合数学,容斥)