题意分析
有 $n$ 个人,第 $i$ 个人的血量为 $a_i$,每一轮每一个存活的人都会让其他所有人的血量减 $1$,血量小于等于 $0$ 死亡。
给出人数 $n$ 和 $x$,满足 $a_i\leq x$($a_i$ 最大值不一定为 $x$),求 $a_i$ 可能的方案数,对 $998244353$ 取模。
考虑 DP。
状态设计
令 $dp_{i,j}$ 表示 $i$ 个人存活,存活的人中最大血量为 $j$ 的方案数。
状态转移
有两种情况:
-
这一轮后,所有人都死了,即 $j\leq i-1$。
那么所有情况总数即最大血量为 $j$ 的总情况数,即最大血量小于等于 $j$ 的方案数减去小于等于 $j-1$ 的方案数,即:
\[dp_{i,j}=j^i-(j-1)^i\] -
这一轮后,有人存活,即 $j>i-1$。
则所有人的血量都减去了 $i-1$,最大血量从 $j$ 变为 $j-i+1$,方案数为:
\[dp_{i,j}=\sum_{k=1}^i\binom{i}{k}dp_{k,j-i+1}(i-1)^{i-k}\]即从 $i$ 个人中选择 $k$ 个人存活,方案数为 $\dbinom{i}{k}dp_{k,j-i+1}$,剩余 $i-k$ 个死人只需要满足原来的最大血量不大于 $i-1$ 即可,方案数为 $(i-1)^{i-k}$。
AC 代码
快速幂的时候写一个记忆化,可以把时间复杂度从 $\mathcal O\left(n^2x\log n\right)$ 优化为 $\mathcal O\left(n^2x\right)$。
不写记忆化则需要卡常(非递归式快速幂)。
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
//#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=500,X=500,P=998244353;
int dp[N+1][X+1],c[N+1][N+1];
int qpow(int a,int n){
static int mem[N+1][N+1];
int &tmp=mem[a][n];
if(tmp){
return tmp;
}
int t=1;
while(n){
if(n&1){
t=1ll*t*a%P;
}
a=1ll*a*a%P;
n>>=1;
}
return tmp=t;
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
int n,x;
scanf("%d %d",&n,&x);
for(int i=0;i<=n;i++){
c[i][0]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
c[i][j]=(c[i-1][j]+c[i-1][j-1])%P;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=x;j++){
if(i-1>=j){
dp[i][j]=(qpow(j,i)-qpow(j-1,i))%P;
}else{
for(int k=1;k<=i;k++){
dp[i][j]=(dp[i][j]+1ll*c[i][k]*dp[k][j-i+1]%P*qpow(i-1,i-k)%P)%P;
}
}
}
}
int ans=0;
for(int i=1;i<=x;i++){
ans=(1ll*ans+dp[n][i])%P;
}
printf("%d\n",(ans<0?ans+P:ans));
/*fclose(stdin);
fclose(stdout);*/
return 0;
}