题解:Arena

CF1606E

Posted by TH911 on March 29, 2025

题目传送门

题意分析

有 $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;
}