[JXOI2018]游戏
[JXOI2018]游戏
链接:https://www.luogu.com.cn/problem/P4562
对于 \(l,r\) 中的数,我们得处理出其中“重要数”的个数,何为“重要数”?即在 \([l,r]\) 这个区间中只有它本身是它的因子的数。这种数在我们检查办公室的过程中是一定要去的。假设我们一共有 \(k\) 个“重要数”。那么最少也要 \(k\) 次,而且走完这 \(k\) 个,全部办公室也就好了。所以 \(t(p)\) 的值,就等于 \(p\) 这个排列中最后一个“重要数” 的位置。我们可以枚举最后一个重要数的位置。那么对于每个位置 \(i\) 对答案的贡献为:
\(C_{i-1}^{k-1}\) 表示在这个位置前要选 \(k-1\) 个位置给剩下 \(k-1\) 个数,\(i\) 表示的则是我们构造出来最后一位重要数的位置为 \(i\) 所有排列 \(p\) 的 \(t(p)\) 值。然后对于 \(k\) 个重要数和 \(r-l+1-k\) 个非重要数,我们的位置是固定的,但是顺序不固定,所以分别乘上一个 \(k!\) 和 \((r-l+1-k)!\) 。
那么接下来我们需要解决求重要数个数的问题。显而易见的,我们可以采取一种类似于埃氏筛的方法来求,代码如下:
for(int i=l;i<=r;++i)
{
if(!p[i])
{
++k;
for(int j=2*i;j<=r;j+=i)
p[j]=1;
}
}
但是这段代码的复杂度是 \(O(n\times\log\log(n))\),处理数据规模为 \(10^7\) 的数据不够快。(值得一提的是,由于本题数据过水,使用类埃氏筛的方法依然是能够通过本题的)我们可以使用时间复杂度为 \(O(n)\) 的欧拉筛处理出每个数的最小的质因子,然后就可以求出每个数最大的因子,代码如下:
for(ll i=2;i<=r;++i)
{
if(!p[i])
{
prime[++prime[0]]=i;
m[i]=i;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=r;++j)
{
p[i*prime[j]]=1;
m[i*prime[j]]=prime[j];
if(i%prime[j]==0) break;
}
}
m[1]=1;
for(ll i=l;i<=r;++i)
if(i/m[i]<l) ++k;
其中 m
数组表示每个数的最小质因子,至于欧拉筛笔者这里不再赘述,可以去看看这篇博客,讲的非常好:欧拉筛法(线性筛)的学习理解。
我们再用快速幂求出 fac[r]
的逆元,然后根据递推式 \(inv_i=inv_{i+1}*(i+1)\)。其中 fac[i]
表示的是 \(i!\) ,inv[i]
表示的是 fac[i]
的逆元。再根据上面我们推出的对答案的贡献式就好了。
总的时间复杂度为 \(O(n)\)。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 1e7+5;
const ll MOD = 1e9+7;
bool p[MAXN];
ll inv[MAXN],fac[MAXN],k,l,r,prime[MAXN],m[MAXN];
ll qpw(ll x,ll b)
{
ll now=1,tmp=x,ans=1;
while(now<=b)
{
if(now&b) ans=ans*tmp%MOD;
tmp=tmp*tmp%MOD;
now<<=1;
}
return ans;
}
ll c(ll n,ll m)
{
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int main()
{
scanf("%d %d",&l,&r);
for(ll i=2;i<=r;++i)
{
if(!p[i])
{
prime[++prime[0]]=i;
m[i]=i;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=r;++j)
{
p[i*prime[j]]=1;
m[i*prime[j]]=prime[j];
if(i%prime[j]==0) break;
}
}
m[1]=1;
for(ll i=l;i<=r;++i)
if(i/m[i]<l) ++k;
fac[0]=inv[0]=1;
for(ll i=1;i<=r;++i)
fac[i]=fac[i-1]*i%MOD;
inv[r]=qpw(fac[r],MOD-2);
for(ll i=r-1;i>=1;--i)
inv[i]=inv[i+1]*(i+1ll)%MOD;
if(l==1)
{
printf("%lld",fac[r-1]*r%MOD*(r+1)%MOD*qpw(2,MOD-2)%MOD);
return 0;
}
ll ans=0;
for(ll i=k;i<=r-l+1;++i)
{
ans+=i*c(i-1,k-1)%MOD*fac[k]%MOD*fac[r-l+1-k]%MOD;
ans%=MOD;
}
printf("%lld",ans);
return 0;
}
有意思的是,在洛谷的评测机上,类埃氏筛的方法比欧拉筛还要快,只能说 \(\log\log(n)\) 是真的小。。。