隐藏
「CQOI2015」选数 - 动态规划+组合数学 | Bill Yang's Blog

路终会有尽头,但视野总能看到更远的地方。

0%

「CQOI2015」选数 - 动态规划+组合数学

题目大意

在区间$[L,R]$中选$N$个数,询问选出来的数最大公约数为$K$的方案数$\bmod\,1000000007$。


题目分析

看到这道题有没有想到莫比乌斯反演?
莫比乌斯反演应该可以做,但是对于这道题就有点卡常数了。

这道题有一种更为快速简洁巧妙的算法:
注意$R-L\le10^5$这个条件,说明我们应该往区间长度入手。

注意到一个性质:若$n$个数不全部相同,那么它们的最大公约数小于它们的极差。证明很显然:当$Max\neq Min$时,$Max-Min=gcd(Max’-Min’)>gcd$。

因此我们可以发现,在一个区间$[L,R]$中选不全部相同数,其最大公约数一定小于$R-L$。
我们将区间中的数都除以$k$,就变成统计$[\frac{L-1}{k}+1,\frac{R}{k}]$中$gcd$等于$1$的方案数。

如何计算$gcd$等于$1$的方案数呢?
我们可以使用容斥原理的动态规划计算。
设$f[i]$为$gcd$是$iK$的方案数,我们可以这样计算:
我们同样可以先堆区间除以$iK$,然后计算$gcd$等于$1$的方案数。
$f[i]=$总方案数$-gcd\neq1$的方案数$-$全部选相同数的方案数。

设当前区间为$(x,y]$(已除以$iK$),总区间长度为$len$(已除以$K$)

这里的$j$是$i$的倍数,因此算法是$O(len\log len)$的。

最后答案则为$f[1]+k$是否在区间内。
因为我们计算的是不全相同的方案数,如果$k$在区间内还可以全部选$k$。


代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
typedef long long LL;
inline const LL Get_Int() {
LL num=0,bj=1;
char x=getchar();
while(x<'0'||x>'9') {
if(x=='-')bj=-1;
x=getchar();
}
while(x>='0'&&x<='9') {
num=num*10+x-'0';
x=getchar();
}
return num*bj;
}
const LL mod=1000000007;
LL n,k,Left,Right,f[100005],len;
bool tmp=0;
LL Quick_Pow(LL a,LL b) {
LL ans=1;
while(b) {
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
int main() {
n=Get_Int();
k=Get_Int();
Left=Get_Int();
Right=Get_Int();
if(Left<=k&&k<=Right)tmp=1;
Left=(Left-1)/k;
Right/=k;
len=Right-Left;
for(int i=len; i>=1; i--) {
int x=Left/i,y=Right/i;
f[i]=((Quick_Pow(y-x,n)-y+x)%mod+mod)%mod;
for(int j=i<<1; j<=len; j+=i)f[i]=((f[i]-f[j])%mod+mod)%mod;
}
printf("%lld\n",f[1]+tmp);
return 0;
}
姥爷们赏瓶冰阔落吧~