题解:Arena

题目传送门

题意分析

\(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)\)

不写记忆化则需要卡常(非递归式快速幂)。

//#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;
}
posted @ 2025-07-21 21:37  TH911  阅读(7)  评论(0)    收藏  举报