快速讨伐

快速讨伐 ( 一道DP与组合数学的思维好题 )

传送门

前置小技巧 (线性求阶乘的逆元)

根据逆元的定义 : (a*x equiv 1 (mod p))
所以相当于 $ x== $ (1over a) (在膜意义下)
那么 (1over !(n+1)) 也就相当于 !(n+1) 的逆元,
所以 !n 的逆元就可以看做 (1over !n) 也就是(1over !(n+1))$ *(n+1)$

ps: 此技巧多用于求组合数时(如下题)

题解

显然不管什么情况操作次数一定是固定的,为 (2*n+) (sum_{i=1}^n) (a_i)

我们先不考虑角色等级的情况

即默认初始等级为 n (即已经操作了n次,且全部用来提升人物等级)
那么对于第i次提升装备等级就可以多打败 Ai 个的敌人,
也就是在剩余的操作中,可以任选 Ai 个位置来进行此操作
则会有 (inom{ 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]* inom{ 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;
}
原文地址:https://www.cnblogs.com/yzhx/p/11530278.html