题意:给你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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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;
}

本文介绍了一种求解特定区间内所有子区间GCD大于等于2的方案数的方法。通过枚举GCD并利用前缀和与快速幂优化计算过程,结合容斥原理去除重复计数,最终实现高效求解。
251

被折叠的 条评论
为什么被折叠?



