前言
正题
题目链接:https://www.luogu.com.cn/problem/P5299
题目大意
有2n2n2n张牌,
- nnn张强化牌,每张上有一个正整数x(x>1)x(x>1)x(x>1),如果使用后之后的每一张攻击牌伤害都会乘上xxx。
- nnn张攻击牌,每张上有一个正整数xxx,使用后造成xxx点伤害。
随机抽上来mmm张,然后按照最优策略打出kkk张的情况下,求所有情况造成的伤害和。
1≤k≤m≤2n≤30001\leq k\leq m\leq 2n\leq 30001≤k≤m≤2n≤3000
解题思路
考虑一个最优策略是啥,显然地我们有强化牌肯定优先打出,直到打完或者只剩最后一费。
因为翻倍至少多一倍的伤害,而我们攻击牌肯定是从大往小选,所以不可能一张攻击牌使得伤害翻倍。
先把两种牌按照数组从大到小排序
我们可以分为两种情况讨论
- 打出k−1k-1k−1张强化牌和一张攻击牌
- 打出<k−1<k-1<k−1张强化牌和若干张攻击牌
第一种情况我们设fif_ifi表示选出了iii张强化牌的所有方案中前kkk张牌乘积的和。
然后枚举一个在k−1∼mk-1\sim mk−1∼m之间的数字iii表示抽到了iii张强化牌,然后再枚举攻击力最大的一张攻击牌,剩下的方案用组合数计算即可。
第二种情况比较麻烦,同样的设f0,if_{0,i}f0,i表示抽了i(i<k)i(i<k)i(i<k)张强化牌的所有方案中所有牌的乘积和。然后设fi,jf_{i,j}fi,j表示总共选了iii张攻击牌和强化牌,打出了前kkk张强化牌和攻击牌时所有强化牌乘积的和,gi,jg_{i,j}gi,j则表示造成的伤害和。
然后转移即可。
时间复杂度:O(nm)O(nm)O(nm)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1e4,P=998244353;
ll T,n,m,k,a[N],b[N],f[N],g[N],fac[N],inv[N],ans;
ll C(ll n,ll m){if(m>n)return 0;return fac[n]*inv[m]%P*inv[n-m]%P;
}
signed main()
{inv[0]=fac[0]=inv[1]=1;for(ll i=2;i<N;i++)inv[i]=P-inv[P%i]*(P/i)%P;for(ll i=1;i<N;i++)fac[i]=fac[i-1]*i%P,inv[i]=inv[i-1]*inv[i]%P;scanf("%d",&T);while(T--){scanf("%lld%lld%lld",&n,&m,&k);ans=0;for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);for(ll i=1;i<=n;i++)scanf("%lld",&b[i]);for(ll i=0;i<=m;i++)f[i]=g[i]=0;f[0]=1;sort(a+1,a+1+n);reverse(a+1,a+1+n);sort(b+1,b+1+n);reverse(b+1,b+1+n);for(ll i=1,x;i<=n;i++)for(ll j=m;j>=1;j--){if(j<k)(f[j]+=f[j-1]*a[i]%P)%=P;else (f[j]+=f[j-1])%=P;}for(ll i=k-1;i<m;i++){for(ll j=1;j<=n;j++)(ans+=f[i]*b[j]%P*C(n-j,m-i-1)%P)%=P;f[i]=0;}for(ll i=1;i<=n;i++){for(ll j=m;j>=1;j--){(f[j]+=f[j-1])%=P;if(j<=k)(g[j]+=g[j-1]+b[i]*f[j-1]%P)%=P;else (g[j]+=g[j-1])%=P;}}printf("%lld\n",(ans+g[m])%P);}return 0;
}