[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}\times i\;\times k!\;\times(r-l+1-k)! \] \(C_{i-1}^{k-1}\)
那麼接下來我們需要解決求重要數個數的問題。顯而易見的,我們可以採取一種類似於埃氏篩的方法來求,程式碼如下:
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)\) 是真的小。。。
路漫漫其修遠兮,吾將上下而求索。