1. 程式人生 > 其它 >[JXOI2018]遊戲

[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}\)

表示在這個位置前要選 \(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)\) 是真的小。。。

路漫漫其修遠兮,吾將上下而求索。