P6046 纯粹容器 解题报告(组合数学)

有一些思维难度的组合数学。
题目链接:https://www.luogu.com.cn/problem/P6046
我们可以把这道题想象成这样一个模型:一根绳子,上面有n个点,每次操作,可以把任意两个相邻的点,也就是一段间隔合并掉。
考虑什么时候第i个容器会消失:也就是遇到一个比自己强的容器的时候。那么,显然考虑被左边或右边第一个比它大的容器打败(不一定是这个容器,因为这个容器可能在此之前就被更强大的容器打败。但是,由不等式的传递性,这个新上位的容器也能打败i)。记左边第一个比i强的位置为 l i l_i li,右边为 r i r_i ri
这里我看到两种解法:

方法1 直接正面算

从期望的定义出发,我们要求的就是 ∑ ( j − 1 ) P ( j ) \sum (j-1)P(j) (j1)P(j),P(j)指的是再第j轮被干掉的概率。
若直接求P(j)单点的情况较为困难,我们考虑容斥:设f(j)为i再j轮内被干掉,好求一些:
设事件A为左侧的区间全部合并完,B为右边的区间全部合并完,则:
f ( j ) = P ( A ) + P ( B ) − P ( A B ) P ( A ) = C n − 1 − ( i − l i ) j − ( i − l i ) C n − 1 j P ( B ) = C n − 1 − ( r i − i ) j − ( r i − i ) C n − 1 j P ( A B ) = C n − 1 − ( r i − l i ) j − ( r i − l i ) C n − 1 j f(j)=P(A)+P(B)-P(AB)\\ P(A)={C_{n-1-(i-l_i)}^{j-(i-l_i)}\over C_{n-1}^{j}}\\ P(B)={C_{n-1-{(r_i-i)}}^{j-{(r_i-i)}}\over C_{n-1}^{j}}\\ P(AB)={C_{n-1-(r_i-l_i)}^{j-(r_i-l_i)}\over C_{n-1}^{j}} f(j)=P(A)+P(B)P(AB)P(A)=Cn1jCn1(ili)j(ili)P(B)=Cn1jCn1(rii)j(rii)P(AB)=Cn1jCn1(rili)j(rili)
对于P(A)、P(B)、P(AB)的的理解:
线段上有n-1个区间可供合并,那么,我们进行k部就有 C n − 1 j C_{n-1}^j Cn1j种方案,其中,能够把左边区间都合并掉的有 C n − 1 − ( i − l i ) j − ( i − l i ) C_{n-1-(i-l_i)}^{j-(i-l_i)} Cn1(ili)j(ili),两个相除就是概率,其他的同理。
之后P(j)=f(j)-f(j-1)计算即可。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
template<typename T>
void read(T&x){
	x=0;
	int f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f*=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+(ch-'0');
		ch=getchar();
	}x*=f;
}
template<typename T>
void write(T x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
//==================================================
typedef long long ll;
const int maxn=50+10;
const int mod=998244353;
int n;
int a[maxn];
int C[maxn][maxn];
int l[maxn],r[maxn];
int fac[maxn];
int finv[maxn];
int ksm(int a,int n){
	int res=1;
	while(n){
		if(n&1)res=1ll*res*a%mod;
		a=1ll*a*a%mod;
		n>>=1;
	}
	return res;
}
void init(){
	fac[0]=1;
	for(int i=1;i<maxn;++i)fac[i]=1ll*fac[i-1]*i%mod;
	finv[maxn-1]=ksm(fac[maxn-1],mod-2);
	for(int i=maxn-2;i>=0;--i){
		finv[i]=1ll*finv[i+1]*(i+1)%mod;
	}
	C[0][0]=1;
	for(int i=1;i<maxn;++i){
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;++j){
			C[i][j]=C[i-1][j]+C[i-1][j-1];
			C[i][j]%=mod;
		}
	}
}
void prelude(){
	for(int i=1;i<=n;++i){
		for(int j=i-1;j>=1;--j){
			if(a[j]>a[i]){
				l[i]=j;
				break;
			}
		}
		for(int j=i+1;j<=n;++j){
			if(a[j]>a[i]){
				r[i]=j;
				break;
			}
		}
	}
}

int tot;

int solve(int idx){
	if(l[idx]==-1&&r[idx]==-1)return n-1;
	//cerr<<idx<<":"<<endl;
	int res=0;
	int pre=0;
	for(int j=1;j<=n-1;++j){
		int pa,pb,pab;
		pa=pb=pab=0;
		if((~l[idx])&&idx-l[idx]<=j){
			pa=1ll*C[n-1-(idx-l[idx])][j-(idx-l[idx])]*ksm(C[n-1][j],mod-2)%mod;
		}
		if((~r[idx])&&r[idx]-idx<=j){
			pb=1ll*C[n-1-(r[idx]-idx)][j-(r[idx]-idx)]*ksm(C[n-1][j],mod-2)%mod;
		}
		if((~r[idx])&&(~l[idx])&&(r[idx]-l[idx])<=j){
			pab=1ll*C[n-1-(r[idx]-l[idx])][j-(r[idx]-l[idx])]*ksm(C[n-1][j],mod-2)%mod;
		}
		//cerr<<j-1<<" "<<((pa+pb-pab-pre)%mod+mod)%mod<<endl;
		res=(res+1ll*(j-1)*((pa+pb-pab-pre)%mod+mod)%mod)%mod;
		pre=((pa+pb-pab)%mod+mod)%mod;
	}
	return res;
}

signed main(){
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	init();
	read(n);
	rep(i,1,n) read(a[i]);
	tot=1;
	rep(i,2,n-1)tot=1ll*i*tot%mod;
	memset(l,-1,sizeof(l));
	memset(r,-1,sizeof(r));
	prelude();
	for(int i=1;i<=n;++i){
		if(i-1)putchar(' ');
		write(solve(i));
	}
	return 0;
}

方法2 考虑一个特殊的方法

其实在这里期望: E = ∑ i = 1 n − 1 P ( x > = i ) E=\sum_{i=1}^{n-1} P(x>=i) E=i=1n1P(x>=i) P ( x > = i ) P(x>=i) P(x>=i)表示存存活回合数>=i的可能性,其实,就是存活1回合的只计算一次,存活2回合的有i=1和i=2两次计算这样理解。剩下的部分和上面差不多。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
template<typename T>
void read(T&x){
	x=0;
	int f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f*=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+(ch-'0');
		ch=getchar();
	}x*=f;
}
template<typename T>
void write(T x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
//==================================================
typedef long long ll;
#define int ll
const int maxn=50+10;
const int mod=998244353;
int n;
int a[maxn];
int C[maxn][maxn];
int l[maxn],r[maxn];
int fac[maxn];
int finv[maxn];
int ksm(int a,int n){
	int res=1;
	while(n){
		if(n&1)res=1ll*res*a%mod;
		a=1ll*a*a%mod;
		n>>=1;
	}
	return res;
}
void init(){
	fac[0]=1;
	for(int i=1;i<maxn;++i)fac[i]=1ll*fac[i-1]*i%mod;
	finv[maxn-1]=ksm(fac[maxn-1],mod-2);
	for(int i=maxn-2;i>=0;--i){
		finv[i]=1ll*finv[i+1]*(i+1)%mod;
	}
	C[0][0]=1;
	for(int i=1;i<maxn;++i){
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;++j){
			C[i][j]=C[i-1][j]+C[i-1][j-1];
			C[i][j]%=mod;
		}
	}
}
void prelude(){
	for(int i=1;i<=n;++i){
		for(int j=i-1;j>=1;--j){
			if(a[j]>a[i]){
				l[i]=i-j;
				break;
			}
		}
		for(int j=i+1;j<=n;++j){
			if(a[j]>a[i]){
				r[i]=j-i;
				break;
			}
		}
	}
}

int tot;


int solve(int idx){
	int res=0;
	for(int j=1;j<n;++j){
		int pa,pb,pab;
		pa=pb=pab=0;
		if((~l[idx])&&j>=l[idx]){
			pa=C[n-1-l[idx]][j-l[idx]]*ksm(C[n-1][j],mod-2);
		}
		if((~r[idx])&&j>=r[idx]){
			pb=C[n-1-r[idx]][j-r[idx]]*ksm(C[n-1][j],mod-2);
		}
		if((~r[idx])&&(~l[idx])&&r[idx]+l[idx]<=j){
			pab=C[n-1-l[idx]-r[idx]][j-l[idx]-r[idx]]*ksm(C[n-1][j],mod-2);
		}
		res=(res+1-(pa+pb-pab))%mod;
		res=(res%mod+mod)%mod;
	}
	return res;
}

signed main(){
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	init();
	read(n);
	rep(i,1,n) read(a[i]);
	tot=1;
	rep(i,2,n-1)tot=1ll*i*tot%mod;
	memset(l,-1,sizeof(l));
	memset(r,-1,sizeof(r));
	prelude();
	for(int i=1;i<=n;++i){
		if(i-1)putchar(' ');
		write(solve(i));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值