[jzoj]4086. 选数(number)(容斥+DP优化)

来源:互联网 发布:编程猫 李天驰 编辑:程序博客网 时间:2024/05/23 13:13

https://jzoj.net/senior/#contest/show/2088/0

Problem

给定一个范围[l..r],求在这个区间内任选N个整数,这N个整数的最大公因数为S的方案数.

Data constraint

100% 1<=N,K,L,R<=109RL<=105

Solution

这是一道非常经典的题目.

我们注意到方案数是排列而并非组合.

那么一个显然的结论:

如果我在M个数里任选N个数,且没有个数,顺序的限定,那么方案数就是MN.

我们再设f(i)表示最大公因数为i时的方案数.

显然有:

f(i)=MNf(2i)f(3i)f(ki)|(ki<=R)

M表示L..R中有多少个数是i的倍数.

那么这个转移是很显然的,但是时间不允许..

我们考虑优化.

优化往往是针对数据的,注意到这题的一个限定性条件“R-L<=10^5”

N=5,K=2,L=80,R=100

如果计算f(2),那么要算出f(4),f(6),f(100).

但我们发下,L..R当中任选两个数的最大公因数不可能超过RL.

所以f(20)以后的f(22),f(24),f(100)都是1,因为它只能选N个自己.

那么显然这一步是很繁琐的.

转化一下状态?

因为我们发现当只选一个数时的方案针对数据来说,太多了!

那我就设成f(i)表示不能只选一个数时的……方案数.

那么每次我可以把那一串对答案贡献为1的直接减掉.

也就是减去一个M.

把那些只选一个数然后能构成最大公因子是i的数全部剪掉.

这样子,因为我们很简单的可以证明,任选两个数的gcd肯定在RL的范围以内.

我们再用f(i)表示构成最大公因数为iK时的……方案数.

这样就可以在O(105)内搞定这题.

减少一些mod操作速度可以更快.

#include <cstdio>#define ll long long#define maxn 200010#define mo 1000000007using namespace std;ll n,K,l,r,ans,g[maxn];ll ksm(ll x,ll y){    ll ans=1;    while (y>0)    {        if ((y&1)==1) ans=(ans*x)%mo;        x=(x*x)%mo;        y=y>>1;    }    return ans;}int main(){    freopen("number.in","r",stdin);    freopen("number.out","w",stdout);       scanf("%lld%lld%lld%lld",&n,&K,&l,&r);    ll i,j,len=(r-l)/K+1;    for (i=len;i>=1;i--)    {        g[i]=((r/(i*K))-((l-1)/(i*K)));        g[i]=ksm(g[i],n)-g[i];        for (j=i*2;j<=len;j+=i) //注意这里是j+=i            g[i]-=g[j];        g[i]=g[i]%mo;    }    if ((l<=K)&&(r>=K)) g[1]++;    printf("%lld",(g[1]+mo)%mo);}
原创粉丝点击