51nod 2589 快速讨伐
如果不考虑升级操作,只有买装备操作和打怪操作,那么首先一定要先买装备,然后可以打死1级的怪,这些怪被打死的时间只要在第一次买装备后面好了,因为现在总操作是\(n+\sum a_i\)个,所以这里的方案数为\(\binom{n-1+\sum_{i=1}a_i}{a_1}a_1!\).然后考虑买第二个装备,这个操作的位置显然在前面操作放完后的剩下的空位中的第一个,然后就有\(a_2\)个怪要打,方案为\(\binom{n-2+\sum_{i=2}a_2}{a_1}a_2!\),容易发现每考虑完\(i\),就剩下一个怪等级为\(i+1\)到\(n\)子问题,所以这个问题答案就是\(n\)个组合数相乘,具体来说是\(\prod_{i=1}^{n}\binom{n-i+\sum_{j=i}a_j}{a_i}a_i!\)
然后考虑有升级操作,那么买第\(i\)个装备就至少要升\(i\)次级,考虑dp,设\(f_{i,j}\)表示升\(i\)次级以及买\(j\)次装备的方案,显然要满足\(i\ge j\),转移就枚举现在升级还是买装备,如果买装备就要考虑这个等级的怪怎么打,也就是乘上一个类似于上面的组合数,就是从剩下的空位选\(a_j\)个打怪并考虑打怪顺序.答案为\(f_{n,n}\)
#include<bits/stdc++.h>
#define LL long long
#define uLL unsigned long long
#define db double
using namespace std;
const int N=5000+10,M=N*N,mod=998244353;
int rd()
{
int x=0,w=1;char ch=0;
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*w;
}
void ad(int &x,int y){x+=y,x-=x>=mod?mod:0;}
int fpow(int a,int b){int an=1;while(b){if(b&1) an=1ll*an*a%mod;a=1ll*a*a%mod,b>>=1;} return an;}
int ginv(int a){return fpow(a,mod-2);}
int n,tt,a[N],s[N],f[N][N];
int fac[M],iac[M];
int C(int a,int b){return b<0||a<b?0:1ll*fac[a]*iac[b]%mod*iac[a-b]%mod;}
int main()
{
n=rd();
tt=n+n;
for(int i=1;i<=n;++i)
{
a[i]=rd();
tt+=a[i],s[i]=s[i-1]+a[i];
}
fac[0]=1;
for(int i=1;i<=tt;++i) fac[i]=1ll*fac[i-1]*i%mod;
iac[tt]=ginv(fac[tt]);
for(int i=tt;i;--i) iac[i-1]=1ll*iac[i]*i%mod;
f[0][0]=1;
for(int i=0;i<=n;++i)
for(int j=0;j<=i;++j)
{
if(!f[i][j]) continue;
ad(f[i+1][j],f[i][j]);
if(j<i) ad(f[i][j+1],1ll*f[i][j]*C(tt-(i-m)-(j+1)-s[j],a[j+1])%mod*fac[a[j+1]]%mod);
}
printf("%d\n",f[n][n]);
return 0;
}