快速讨伐
快速讨伐 ( 一道DP与组合数学的思维好题 )
传送门
前置小技巧 (线性求阶乘的逆元)
根据逆元的定义 : \(a*x \equiv 1 (mod p)\)
所以相当于 $ x== $ \(1\over a\) (在膜意义下)
那么 \(1\over !(n+1)\) 也就相当于 !(n+1) 的逆元,
所以 !n 的逆元就可以看做 \(1\over !n\) 也就是\(1\over !(n+1)\)$ *(n+1)$
ps: 此技巧多用于求组合数时(如下题)
题解
显然不管什么情况操作次数一定是固定的,为 \(2*n+\) \(\sum_{i=1}^n\) \(a_i\) 。
我们先不考虑角色等级的情况
即默认初始等级为 n (即已经操作了n次,且全部用来提升人物等级)
那么对于第i次提升装备等级就可以多打败 Ai 个的敌人,
也就是在剩余的操作中,可以任选 Ai 个位置来进行此操作
则会有 \(\binom{ n-i+\sum_{k=i}^n Ak}{a_i}\) 种选择
(\(\sum_{k=i}^n Ak\) 是A从i开始的后缀和)
然后显然递推下去就好
再考虑普通情况
那么只多了一个操作限制,就是人物等级必须不小于装备等级
不妨DP一下,设 f[i][j] 表示人物已经 i 级,装备 j 级,则显然 可以由f[i][j]推出 f[i+1][j] 与 f[i][j+1]
而对于 f[i+1][j] 显然和 f[i][j] 的剩余操作选择方案是一样的
那么
我们再来考虑一下 f[i][j+1] 的情况
通过之前的特殊情况,若多升了一次装备等级,则选择方案会等于
\(f[i][j]* \binom{ n+n-i-j-1+\sum_{k=j+1}^n Ak}{a_{j+1}}\)
代码
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
int t=0; char ch=get;
while( ch<'0' || ch>'9') ch=get;
while( ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
return t;
}
const int _=5005;
const int mod=998244353;
ll sum[_],n,m,a[_],c1[_],c2[_],f[_][_];
in ll fastpow(ll a,int b)
{
ll res=1;
while(b)
{
if(b%2==1)
res=(res*a)%mod;
a=a*a%mod,b>>=1;
}
return res;
}
in ll C(ll x,ll y)
{
return c1[x]*c2[y]%mod*c2[x-y]%mod;
}
int main()
{
n=read(),m=0;
for(re int i=1;i<=n;i++)
a[i]=read();
for(re int i=n;i;i--) sum[i]=(sum[i+1]+a[i])%mod; //sum存的是后缀和
int tot=2*n+sum[1]; c1[0]=1;
for(re int i=1;i<=tot;i++) c1[i]=(c1[i-1]*i)%mod; // c1中存储阶乘
c2[tot]=fastpow(c1[tot],mod-2);
for(re int i=tot-1;i>=0;i--)c2[i]=c2[i+1]*(i+1)%mod; // c2中存储阶乘的逆元
f[m][0]=1;
for(re int i=m;i<=n;i++)
for(re int j=0;j<=i;j++)
if(f[i][j])
{
if(i<n) f[i+1][j]=(f[i+1][j]+f[i][j])%mod;
if(j<i)
f[i][j+1]=(f[i][j+1]+(f[i][j]*C(n+n-i-j+sum[j+1]-1,a[j+1]))%mod)%mod;
}
cout<<f[n][n]<<endl;
}
嗯,就这样了...