题解:重返现世

洛谷P4707 | kth Min-max 容斥 | DP

Posted by TH911 on July 15, 2025

题目传送门

题意分析

kth Min-max 容斥

首先可以去除掉 $p$ 中的 $0$,因为 $p_i=0$ 的 $i$ 不会生成,对答案没有任何影响。

求收集到 $k$ 种原料的期望时间,即求收集所有原料的期望时间的第 $k$ 大时间。

那么就可以考虑 kth Min-max 容斥。

定义 \(\operatorname*{kthmin}\) 表示求第 $k$ 小,\(\operatorname*{kthmax}\) 表示求第 $k$ 大。

设 $S=\set{1,2,3,\cdots,n}$,$t_i=\dfrac{m}{p_i}$ 表示 $i$ 被收集的期望时间。

集合 $T$ 中的所有元素第一次被收集的期望时间为:

\[E\left(\min_{i\in T}t_i\right)=\dfrac{1}{\sum\limits_{i\in T}\frac{p_i}{m}}=\dfrac{m}{\sum\limits_{i\in T}p_i}\]

则题目所求即:

\[E\left(\operatorname*{kthmin}_{i\in S}t_i\right)\]

由 kth Min-max 容斥,则有:

\[E\left(\operatorname*{kthmin}_{i\in S} t_i\right)=\sum_{T\subseteq S}(-1)^{\vert T\vert-k}\dbinom{\vert T\vert-1}{k-1}E\left(\max_{i\in T}t_i\right)\\ E\left(\operatorname*{kthmax}_{i\in S} t_i\right)=\sum_{T\subseteq S}(-1)^{\vert T\vert-k}\dbinom{\vert T\vert-1}{k-1}E\left(\min_{i\in T}t_i\right)\]

$\max t_i$ 实际上并不好求,所以为了利用 kth Min-max 容斥,我们可以将题目所求转化一下。

令 $k’=n-k+1$,则题目所求为(以下 $k’$ 适用于 \(\operatorname*{kthmax},\operatorname*{kthmin}\)):

\[\begin{aligned} E\left(\operatorname*{kthmax}_{i\in S} t_i\right)&=\sum_{T\subseteq S}(-1)^{\vert T\vert-k}\dbinom{\vert T\vert-1}{k-1}E\left(\min_{i\in T}t_i\right)\\ &=m\sum_{T\subseteq S}(-1)^{\vert T\vert-k}\dbinom{\vert T\vert-1}{k-1}\dfrac{1}{\sum\limits_{i\in T}p_i} \end{aligned}\]

注意到这道题目不像[PKUWC2018] 随机游走那般,可以直接枚举 $T$,因此需要优化

DP 求解

DP 状态

考虑 DP。因为 $k’\leq11,m\leq10000,\sum\limits_{i=1}^np_i=m$,考虑与之有关的 DP。

设 \(\textit{dp}_{i,j,k}\) 表示考虑 \(S=\set{1,2,3,\cdots,i},\sum\limits_{l\in T}p_l=j\),\(\sum\limits_{T\subseteq S}(-1)^{\vert T\vert-k}\dbinom{\vert T\vert-1}{k-1}\) 的值(下文中 $k$ 均不为题中 $k$),即:

\[\textit{dp}_{i,j,k}=\sum_{T\subseteq\set{1,2,3,\cdots,i}}(-1)^{\vert T\vert-k}\dbinom{\vert T\vert-1}{k-1}\]

DP 转移

  • 如果 $i\not\in T$,则有方案数为:

    \[\textit{dp}_{i-1,j,k}\]
  • 否则 $i\in T$,去除 $i$ 的概率 $p_i$,方案数即 $\set{1,2,3,\cdots,i-1}$ 的方案数。

    考虑组合数递推公式

    \[\dbinom{\vert T\vert-1}{k-1}=\dbinom{(\vert T\vert-1)-1}{k-1}+\dbinom{(\vert T\vert-1)-1}{(k-1)-1}\]

    那么,可以发现在 $\dbinom{(\vert T\vert-1)-1}{(k-1)-1}$ 中 $k$ 发生了 $1$ 的变化,因此乘上 $-1$。

    故,有方案数为:

    \[\textit{dp}_{i-1,j-p_i,k-1}-\textit{dp}_{i-1,j-p_i,k}\]

故,有:

\[\textit{dp}_{i,j,k}=\textit{dp}_{i-1,j,k}+\textit{dp}_{i-1,j-p_i,k-1}-\textit{dp}_{i-1,j-p_i,k}\]

DP 边界

\[dp_{i,0,0}=1\]

因为发现这样做是正确的,容斥函数中,$T=\varnothing$ 是空集(因为概率和为 $0$),$(-1)^{0-0}=1$。

但其实意义也不大,仅仅是为了保证后面有意义的地方计算正确而已。

答案统计

枚举概率和 $i$,则有答案 $\textit{ans}$ 为:

\[\textit{ans}=\sum_{i=1}^mm\cdot dp_{n,i,k'}\cdot\dfrac{1}{i}\]

对于模意义下的 $\dfrac1i$,使用乘法逆元即可。

滚动数组优化

这样的空间复杂度为 $\mathcal O(nmk’)$,会炸掉。

但是注意到 $\textit{dp}_i$ 与 \(\textit{dp}_{i-2},\textit{dp}_{i-3},\cdots,\textit{dp}_0\) 均无关,因此可以使用滚动数组滚掉 $i$ 这一维。

AC 代码

时间复杂度:$\mathcal O(nmk’)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=1000,M=10000,K=11,P=998244353;
int n,k,m,p[N+1],dp[2][M+1][K+1];
int qpow(int base,int n){
	int ans=1;
	while(n){
		if(n&1){
			ans=1ll*ans*base%P;
		}
		base=1ll*base*base%P;
		n>>=1;
	}
	return ans;
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n>>k>>m;
	k=n-k+1;
	for(int i=1;i<=n;i++){
		cin>>p[i];
		if(!p[i]){
			i--,n--;
		}
	}
	dp[0][0][0]=dp[1][0][0]=1; 
	bool mode=0;
	for(int i=1;i<=n;i++){
		mode=!mode;
		memset(dp[mode],0,sizeof(dp[mode]));
		dp[mode][0][0]=1;
		for(int j=1;j<p[i];j++){
			for(int kk=1;kk<=k;kk++){
				dp[mode][j][kk]=dp[!mode][j][kk];
			}
		}
		for(int j=p[i];j<=m;j++){
			for(int kk=1;kk<=k;kk++){
				dp[mode][j][kk]=((1ll*dp[!mode][j][kk] + dp[!mode][j-p[i]][kk-1])%P - dp[!mode][j-p[i]][kk])%P;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=m;i++){
		ans=(ans+1ll*dp[mode][i][k]*qpow(i,P-2)%P)%P;
	}
	ans=1ll*ans*m%P;
	if(ans<0){
		ans+=P;
	}
	cout<<ans<<'\n';
	
	cout.flush(); 
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}