zoukankan      html  css  js  c++  java
  • [PKUWC2018]Slay the Spire

    题目

    我竟然能做出九老师的组合计数题,尽管这题很呆

    我们先考虑一个简单的问题,如果给定你要选出来的(m)张卡牌,如何做到攻击伤害最高

    非常显然,因为保证了强化牌上的数字大于(1),所以我们优先选择那些强化牌,毕竟最小的一张只能翻两倍的强化牌都要比选择攻击牌好;选完强化牌之后剩下的攻击牌自然是越大越好

    当然了,我们也不可能选出(k)张强化牌来,这样什么伤害都造不成,所以如果强化牌数量大于(k),我们就选择前(k-1)大的强化牌,配上最大的攻击牌

    我们发现给定的强化牌和攻击牌都是无序的,我们先排序再说

    之后我们就可以大力(dp)

    (dp_{i,j})表示前(i)张强化牌里选出(j)张所有强化牌乘积的和,(f_{i,j})表示前(i)张强化牌里选择了(j)张且第(i)张一定被选择的乘积和

    显然有转移

    [dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1} imes a_i ]

    [f_{i,j}=dp_{i-1,j-1} imes a_i ]

    攻击牌这边我们设(h_{i,j})表示前(i)张攻击牌里选择了(j)张的所有方案的和,(g_{i,j})表示强迫选择第(i)

    也有转移

    [h_{i,j}=h_{i-1,j}+h_{i-1,j-1}+C_{i-1}^{j-1} imes b_i ]

    [g_{i,j}=h_{i-1,j-1}+C_{i-1}^{j-1} imes b_i ]

    现在我们考虑枚举选择了多少张强化牌

    设选择了(i)张强化牌,自然也就需要选择(m-i)张攻击牌

    如果(i<k),那么这些强化牌都是要直接用的,选择的(m-i)张攻击牌里能被用于打出的(k)张的也就只有(k-i)张,而剩下的(m-i-(k-i)=m-k)张牌我们在后面随便选一下

    于是答案就是

    [sum_{j=0}^nf_{j,i} imes sum_{j=0}^ng_{j,k-i}C_{n-j}^{m-k} ]

    如果(i>=k),那么我们也只能选择(k-1)张强化牌,剩下的多选出来的(i-k+1)张我们还是从后面的位置里选出来,实际上需要的攻击牌也就只有(1)张,需要额外多选的攻击牌也就只有(m-i-1)

    答案就是

    [sum_{j=0}^nf_{j,k-1} C_{n-j}^{i-k+1} imes sum_{j=0}^ng_{j,1}C_{n-j}^{m-i-1} ]

    代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define re register
    #define LL long long
    #define max(a,b) ((a)>(b)?(a):(b))
    #define min(a,b) ((a)<(b)?(a):(b))
    inline int read() {
    	char c=getchar();int x=0;while(c<'0'||x>'9') c=getchar();
    	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
    }
    const int mod=998244353;
    const int maxn=3005;
    int c[maxn][maxn];
    int dp[maxn][maxn],f[maxn][maxn];
    int g[maxn][maxn],h[maxn][maxn];
    int T,n,m,k;
    int a[maxn],b[maxn];
    inline int C(int n,int m) {if(m>n) return 0;return c[n][m];}
    inline int cmp(int A,int B) {return A>B;}
    int main() {
    	T=read();int M=maxn-5;
    	for(re int i=0;i<=M;i++) c[i][0]=c[i][i]=1;
    	for(re int i=1;i<=M;i++)
    		for(re int j=1;j<i;j++)
    			c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    	while(T--) {
    		n=read(),m=read(),k=read();
    		for(re int i=1;i<=n;i++) a[i]=read();
    		for(re int i=1;i<=n;i++) b[i]=read();
    		std::sort(a+1,a+n+1,cmp);std::sort(b+1,b+n+1,cmp);
    		dp[0][0]=1;
    		for(re int i=1;i<=n;i++)
    			for(re int j=0;j<=i;j++) {
    				dp[i][j]=dp[i-1][j];
    				if(j) dp[i][j]=(dp[i][j]+1ll*dp[i-1][j-1]*a[i]%mod)%mod;
    			}
    		f[0][0]=1;
    		for(re int i=1;i<=n;i++) 
    			for(re int j=1;j<=i;j++) 
    				f[i][j]=1ll*dp[i-1][j-1]*a[i]%mod;
    		h[0][0]=0;
    		for(re int i=1;i<=n;i++)
    			for(re int j=0;j<=i;j++) {
    				h[i][j]=h[i-1][j];
    				if(j) h[i][j]=(h[i][j]+1ll*C(i-1,j-1)*b[i]%mod+h[i-1][j-1])%mod;
    			}
    		for(re int i=1;i<=n;i++)
    			for(re int j=1;j<=i;j++)
    				g[i][j]=(1ll*C(i-1,j-1)*b[i]%mod+h[i-1][j-1])%mod;
    		int ans=0;
    		for(re int i=0;i<m;i++) {
    			int now=0;
    			for(re int j=0;j<=n;j++) {
    				if(i<=k-1) now=(now+f[j][i])%mod;
    					else now=(now+1ll*f[j][k-1]*C(n-j,i-k+1)%mod)%mod;
    			}
    			int tot=0,res=0;
    			if(i<=k-1) res=k-i;else res=1;
    			for(re int j=0;j<=n;j++) 
    				tot=(tot+1ll*g[j][res]*C(n-j,m-i-res)%mod)%mod;
    			ans=(ans+1ll*now*tot%mod)%mod;
    		}
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Java实现 LeetCode 382 链表随机节点
    Java实现 LeetCode 382 链表随机节点
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 380 常数时间插入、删除和获取随机元素
    Java实现 LeetCode 380 常数时间插入、删除和获取随机元素
    Linux下的iwpriv(iwlist、iwconfig)的简单应用
    OCX控件的注册卸载,以及判断是否注册
    .OCX、.dll文件注册命令Regsvr32的使用
  • 原文地址:https://www.cnblogs.com/asuldb/p/10758306.html
Copyright © 2011-2022 走看看