有一些思维难度的组合数学。
题目链接: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)
∑(j−1)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)=Cn−1jCn−1−(i−li)j−(i−li)P(B)=Cn−1jCn−1−(ri−i)j−(ri−i)P(AB)=Cn−1jCn−1−(ri−li)j−(ri−li)
对于P(A)、P(B)、P(AB)的的理解:
线段上有n-1个区间可供合并,那么,我们进行k部就有
C
n
−
1
j
C_{n-1}^j
Cn−1j种方案,其中,能够把左边区间都合并掉的有
C
n
−
1
−
(
i
−
l
i
)
j
−
(
i
−
l
i
)
C_{n-1-(i-l_i)}^{j-(i-l_i)}
Cn−1−(i−li)j−(i−li),两个相除就是概率,其他的同理。
之后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=1∑n−1P(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;
}