2017 Multi-University Training Contest - Team 2 1009 HDU 6053 TrickGCD(分段 容斥)

题意:给你n个数字,每个位置的数可以小于等于a[i],求所有区间gcd(l,r)都满足大于等于2的方案数


思路:我们可以枚举gcd,然后a[i]/gcd就是i位置能够填的数的个数,然后每个位置累乘起来就能得到数列为gcd时的方案数。但是这

是n^2复杂度,显然会T,因为a[i]/cgd有许多是相同的,我们可以将相同的一起考虑,这可以用前缀和和快速幂解决。这样算完之

后显然计算了许多重复的,


容斥的思路(点击打开链接):num[i]表示gcd为i时求出的符合情况数,显然它包含了i的倍数对应的情况,我们需要把这些数减去,

是倍数的情况显然也有重复,比较复杂。这个过程我们可以倒过来从上界往下去容斥,这样每次对于num[i]来说,它的倍数的情况

都是已经处理好了的,只要依次减去倍数的情况了,其实相当于一个dp的过程。。



代码:

#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
const int mod = 1e9+7;
int n, a[maxn], sum[maxn];
ll dp[maxn];

ll qpow(ll p, int q)
{
    ll ans = 1;
    while(q)
    {
        if(q%2) ans = ans*p%mod;
        p = p*p%mod;
        q /= 2;
    }
    return ans;
}

int main(void)
{
    int t, ca = 1;
    cin >> t;
    while(t--)
    {
        memset(sum, 0, sizeof(sum));
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
        {
            int tmp;
            scanf("%d", &tmp);
            sum[tmp]++;
        }
        for(int i = 1; i < maxn; i++)
            sum[i] += sum[i-1];
        for(int i = 2; i < maxn; i++)   //枚举gcd
        {
            dp[i] = 1;
            for(int j = 0; j < maxn; j+=i)  //分段
            {
                int cnt;
                if(j+i-1 >= maxn ) cnt = sum[maxn-1]-sum[j-1];
                else if(j == 0) cnt = sum[j+i-1];
                else cnt = sum[j+i-1]-sum[j-1];
                int p = j/i;
                if(p == 0 && cnt) dp[i] = 0;
                else if(cnt) dp[i] = (dp[i]*qpow(p, cnt))%mod;
            }
        }
        for(int i = maxn-1; i >= 2; i--)    //容斥
        {
            for(int j = i+i; j < maxn; j+=i)
                dp[i] -= dp[j], dp[i] = (dp[i]%mod+mod)%mod;
        }
        ll ans = 0;
        for(int i = 2; i < maxn; i++)
            ans += dp[i], ans %= mod;
        printf("Case #%d: %lld\n", ca++, ans);
    }
    return 0;
}


你可能感兴趣的:(思维,组合数学)