P3600-隨機數生成器【dp,數學期望】

正題

題目鏈接:https://www.luogu.com.cn/problem/P3600


題目大意

nn個數的序列,每個數是[1..x][1..x]中的一個,有qq個區間[l..r][l..r],求所有區間最小值的最大值的期望。


解題思路

首先如果一個區間包含別的區間,那麼這個區間顯然不需要考慮,所以去掉後左端點和右端點同時遞增,考慮最大值不超過ii的方案hih_i,定義gig_i表示放ii個點,每個區間至少包括一個點的方案。
hi=gjij(xi)njh_i=g_j*i^j*(x-i)^{n-j}

然後fi,jf_{i ,j}表示前ii個位置,ii有點,總共放了jj個點的方案,考慮轉移,k>jk->j時要保證kik\sim i這個區間內沒有一個完整的區間,預處理好後有fi,j=fk,j1f_{i,j}=\sum f_{k,j-1}

我們發現kk的取值是一段區間,前綴和優化即可。


codecode

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=2100,XJQ=666623333;
struct node{
	ll l,r;
}a[N],b[N];
ll n,x,q,f[N][N],s[N][N],fl[N],fr[N],h[N],g[N],tot,ans;
bool cmp(node x,node y)
{return (x.l==y.l)?(x.r<y.r):(x.l<y.l);}
ll power(ll x,ll b){
	ll ans=1;
	while(b){
		if(b&1)ans=ans*x%XJQ;
		x=x*x%XJQ;b>>=1;
	}
	return ans;
}
int main()
{
	scanf("%lld%lld%lld",&n,&x,&q);
	for(ll i=1;i<=q;i++)
		scanf("%lld%lld",&b[i].l,&b[i].r);
	sort(b+1,b+1+q,cmp);
	for(ll i=1;i<=q;i++){
		if(i>1&&b[i].l==b[i-1].l)continue;
		while(tot&&a[tot].r>=b[i].r)tot--;
		a[++tot]=b[i];
	}
	memset(fl,0x3f,sizeof(fl));
	memset(fr,0xcf,sizeof(fr));
	for(ll i=1;i<=tot;i++)	
		for(ll j=a[i].l;j<=a[i].r;j++)
			fl[j]=min(fl[j],i),fr[j]=max(fr[j],i);
	ll l=0;fl[0]=fr[0]=0;
	for(int i=1;i<=n;i++)
		if(fr[i]<0)fl[i]=l+1,fr[i]=l;
		else l=max(l,fr[i]);
	l=0;f[0][0]=s[0][0]=1;
	for(ll i=1;i<=n;i++){
		while(l<i-1&&fr[l]+1<fl[i])l++;
		for(int j=1;j<=i;j++)
			if(l)f[i][j]=(s[i-1][j-1]-s[l-1][j-1]+XJQ)%XJQ;
			else f[i][j]=s[i-1][j-1];
		for(int j=0;j<=i;j++)
			s[i][j]=(s[i-1][j]+f[i][j])%XJQ;
	}
	for(ll i=1;i<=n;i++)
		if(fr[i]==tot)
			for(ll j=1;j<=n;j++)
				(g[j]+=f[i][j])%=XJQ;
	for(ll i=1;i<=x;i++)
		for(ll j=1;j<=n;j++)
			h[i]=(h[i]+g[j]*power(i,j)%XJQ*power(x-i,n-j)%XJQ)%XJQ;
	ll z=power(power(x,n),XJQ-2);
	for(ll i=n;i>=1;i--)
		h[i]=(h[i]-h[i-1]+XJQ)%XJQ;
	for(ll i=1;i<=n;i++)
		ans=(ans+h[i]*i%XJQ*z%XJQ)%XJQ;
	printf("%lld",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章