[Codeforces 1295F]Good Contest(DP+組合數學)


[Codeforces 1295F]Good Contest(DP+組合數學)

題面

有一個長度為\(n\)的整數序列,第\(i\)個數的值在\([l_i,r_i]\)中隨機產生。問這個序列是一個不上升序列的概率(模\(998244353\)意義下)。

\(n \leq 50,l_i,r_i \leq 998244351\)

分析

[APIO2016]划艇幾乎一模一樣。可惜比賽的時候時間不夠。

首先把問題轉化成求最長不上升序列的數量。

我們把這些區間離散化,分割成兩兩之間不互相覆蓋的若干個區間,把這些分割后的區間從被拆分成編號在\([l_i,r_i)\)的新區間。如原來的區間為\([1,4],[2,3],[4,5]\).那么拆分出來的新區間有\(1:[1,1],2:[2,3],3:[4,4],4:[5,5]\).而新的\(l_i,r_i\)對應為\([1,3),[2,3),[3,5)\).下面的代碼實現了該過程.

void discrete(){
	dn=0;
	for(int i=1;i<=n;i++){
		tmp[++dn]=a[i].l;
		tmp[++dn]=a[i].r+1;//轉成開區間 
	}
	sort(tmp+1,tmp+1+dn);
	dn=unique(tmp+1,tmp+1+dn)-tmp-1;
	for(int i=1;i<=n;i++){
		a[i].l=lower_bound(tmp+1,tmp+1+dn,a[i].l)-tmp;
		a[i].r=lower_bound(tmp+1,tmp+1+dn,a[i].r+1)-tmp;
	}
}

\(dp[i][j]\)表示前\(i\)個數,第\(i\)個數在第\(j\)個新區間或之后的新區間內的方案數。那么從前面轉移過來時的區間編號一定\(\geq j\). 於是可以寫出轉移方程:

\[dp[i][j]=\sum_{1 \leq k \leq i,j \in[l_k,r_k] } dp[k-1][j+1] \times C_{i-k+len(j)}^{i-k+1} \]

我們枚舉最前面的與\(i\)在同一個新區間\(j\)的位置\(k\), 那么 比\(k\)小的位置所在區間編號一定\(>j\),所以乘上\(dp[k-1][j+1]\). 而\(len(j)\)表示的是第\(j\)個新區間的實際長度,組合數表示的是從區間\(j\)里選出\(i-k+1\)個遞增的數.

注意到上面的轉移方程只求出了第\(i\)個數恰好在區間\(j\)里的方案數。所以枚舉完\(j\)后還要求一遍后綴和。

最終答案為\(\frac{dp[n][1]}{\prod_{i=1}^n (r_i-l_i+1)}\)

dp轉移的復雜度為\(O(n^3)\),但是由於\(len(j)\)比較大而\(i-k+1\)比較小.,求組合數需要\(O(n)\)的時間枚舉。總復雜度為\(O(n^4)\)

代碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 998244353
#define maxn 1000
using namespace std;
typedef long long ll; 
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){ 
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	} 
	return ans;
}
inline ll inv(ll x){
	return fast_pow(x,mod-2); 
} 
ll fact[maxn+5],invfact[maxn+5],invx[maxn+5];
void ini_inv(int n){
	fact[0]=1;
	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
	invfact[n]=inv(fact[n]);
	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
	for(int i=1;i<=n;i++){
		invx[i]=invfact[i]*fact[i-1]%mod;
	}
}
inline ll C(ll n,ll m){
	ll ans=1;
	for(int i=n;i>=n-m+1;i--) ans=ans*i%mod;
	for(int i=1;i<=m;i++) ans=ans*invx[i]%mod;
	return ans;
}
int n;
struct seg{
	int l;
	int r;
}a[maxn+5];
int dn=0;
int tmp[maxn+5];//離散化用 
void discrete(){
	dn=0;
	for(int i=1;i<=n;i++){
		tmp[++dn]=a[i].l;
		tmp[++dn]=a[i].r+1;//轉成開區間 
	}
	sort(tmp+1,tmp+1+dn);
	dn=unique(tmp+1,tmp+1+dn)-tmp-1;
	for(int i=1;i<=n;i++){
		a[i].l=lower_bound(tmp+1,tmp+1+dn,a[i].l)-tmp;
		a[i].r=lower_bound(tmp+1,tmp+1+dn,a[i].r+1)-tmp;
	}
}

ll dp[maxn+5][maxn+5];//dp[i][j]表示前i個數,第i個數在j及之后區間內的方案數
					 //枚舉放在第j個區間里的個數,dp[i][j]+=dp[k-1][j+1]*C 

ll sum[maxn+5];

int main(){
	ini_inv(maxn);
	scanf("%d",&n);
	ll all=1; 
	for(int i=1;i<=n;i++){
		scanf("%d %d",&a[i].l,&a[i].r);
		all=all*(a[i].r-a[i].l+1)%mod;
	} 
	discrete();
	for(int j=1;j<=dn;j++) dp[0][j]=1;
	for(int i=1;i<=n;i++){
		for(int j=a[i].l;j<a[i].r;j++){
			for(int k=i;k>0;k--){
				if(j<a[k].l||j>=a[k].r) break;
				dp[i][j]+=dp[k-1][j+1]*C(i-k+tmp[j+1]-tmp[j],i-k+1)%mod;
				//插板法,把n個物品分成m份,允許有空,C(n+m-1,m)
				//也就是說把r-l+1分成i-k+1份,再加上l,就可以保證在[l,r]內 
				dp[i][j]%=mod;
			}
		}
		for(int j=dn-1;j>=1;j--){
			dp[i][j]+=dp[i][j+1];
			dp[i][j]%=mod;
		}
	}
	printf("%lld\n",dp[n][1]*inv(all)%mod);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM