LOJ2538:「PKUWC2018」Slay the Spire

LOJ

题意

给你 2 n 2n 2n张牌, n n n张攻击牌, n n n张强化牌,每次随机等概率选出 m m m张牌,且以最优决策打出 k k k张( k ≤ m k\leq m km),问期望能造成多少伤害;

题解

因为最后的答案要乘上 ( 2 n ) ! m ! ( 2 n − m ) ! \frac{(2n)!}{m!(2n-m)!} m!(2nm)!(2n)!,所以其实要求的就是所有选牌方案以最优决策打出造成的伤害总和;首先这题给了个非常重要的条件保证强化牌的数值大于等于2,这意味着什么呢?意味着如果同时有强化牌和攻击牌时,肯定要优先打出强化牌(当然至少要保证打出一张攻击牌),这个结论是显然的,考虑如果这里有三张牌分别是攻击牌( 3 3 3)攻击牌( 3 3 3)强化牌( 2 2 2),你要打出两张,如果考虑优先打攻击牌那么最优的情况就是现在这种最大攻击力是一样的,那么造成的伤害最多也就是在一张的基础上翻一倍,而强化牌能让那一张牌的攻击力至少翻一倍,这结论就是显然的了;那么现在我们就有哪些打出牌的情况以及怎么抽这样的牌;

  • 第一种
    因为要优先考虑强化牌,所以第一种牌型是有强化牌打强化牌,剩下的至少保证打一张攻击牌,同时剩的 m − k m-k mk张牌全部抽的是攻击牌;
  • 第二种
    上面那种情况肯定是不全面的,为什么剩下的 m − k m-k mk张不能抽强化牌呢,现在考虑什么时候剩下的牌会抽到强化牌,那么一定是只打出一张攻击牌时,才能剩余强化牌,否则你打两张打三张攻击牌为什么不先打这剩的强化牌?

所以总结一下,一共只有两种抽牌类型(不打攻击牌的直接不考虑因为伤害为 0 0 0):

  1. 抽法: m m m张牌中有 0 0 0 m i n ( K − 1 , n ) min(K-1,n) min(K1,n)张强化牌,剩的全是攻击牌
    打法:先把强化牌全部打出,再把剩下的攻击牌按攻击力从大到小打出
  2. 抽法: m m m张牌中任意张攻击牌任意张强化牌,加起来为 m m m就行了
    打法:只打一张攻击力最高的,剩下的全打强化牌

好了现在思路就非常清晰了,现在就该考虑这么去找这些牌型的牌;
首先考虑第一种,从大到小先把攻击牌和强化牌分别排序,然后定义 F [ i ] [ j ] F[i][j] F[i][j]表示到考虑到第 i i i张牌时选 j j j张牌出来打还要选 m − k m-k mk张不打的方案伤害总和,转移为 F [ i ] [ j ] = C [ n − i ] [ m − k ] ∗ ∑ k = 0 i − 1 ( F [ k ] [ j − 1 ] + A [ i ] ) F[i][j]=C[n-i][m-k]*\sum_{k=0}^{i-1}(F[k][j-1]+A[i]) F[i][j]=C[ni][mk]k=0i1(F[k][j1]+A[i]) C C C为组合数,但是这个这样的话就是 O ( n 3 ) O(n^3) O(n3)的复杂度了,绝对跑不过去;但是想一想我们是从哪些地方转移过来的?其实我们是从每一个选 j − 1 j-1 j1张牌的方案转移过来的,那么现在修改状态 F [ i ] F[i] F[i]表示按顺序考虑到当前牌时选 i i i张打 m − k m-k mk张不打的方案伤害总和,再加上一个辅助数组 G [ i ] G[i] G[i],表示按顺序考虑到当前牌 − 1 -1 1时选 i i i张出来打的方案伤害总和,那么转移是这样的(t表示当前考虑到的牌的序号): G [ i ] = G [ i − 1 ] + A [ t ] ∗ C [ t − 1 ] [ i − 1 ] G[i]=G[i-1]+A[t]*C[t-1][i-1] G[i]=G[i1]+A[t]C[t1][i1] F [ i ] = F [ i ] + ( G [ i − 1 ] + A [ t ] ∗ C [ t − 1 ] [ i − 1 ] ) ∗ C [ n − t ] [ m − k ] F[i]=F[i]+(G[i-1]+A[t]*C[t-1][i-1])*C[n-t][m-k] F[i]=F[i]+(G[i1]+A[t]C[t1][i1])C[nt][mk] G G G就相当于在原来选 i − 1 i-1 i1的基础上每个方案后面跟一个 A [ t ] A[t] A[t]跟的个数就是在前 t − 1 t-1 t1张牌中选 i − 1 i-1 i1张;
F F F就是在原有的基础上加上当前牌的贡献,因为当前牌是打出的最后一张,所以说要在它后面的 n − t n-t nt张牌中来选,乘上选的方案数就好了;
再考虑强化牌怎么选,因为不用考虑多抽,其实就是跟 G G G转移差不多的,再前面的基础上添上一张牌就是新的方案贡献, H [ i ] H[i] H[i]表示考虑到当前这张牌时选 i i i张强化的强化值总和: H [ i ] = H [ i ] + H [ i − 1 ] ∗ B [ t ] H[i]=H[i]+H[i-1]*B[t] H[i]=H[i]+H[i1]B[t]
第一种牌型所需要的东西就准备齐全了,现在怎么算贡献呢,就挨着挨着考虑打多少张攻击牌就完事了: A n s 1 = ∑ i = 1 k F [ i ] ∗ H [ k − i ] Ans_1=\sum_{i=1}^kF[i]*H[k-i] Ans1=i=1kF[i]H[ki]
好,现在来考虑第二种牌型,需要的东西也比较简单, W [ i ] W[i] W[i]表示选当前牌作为唯一一张打出去的牌时再多选 i i i张不打的方案伤害总和,因为当前牌要打出去,那么其他不打的牌肯定只能选小于等于它的牌 W [ i ] = W [ i ] + A [ t ] ∗ C [ n − t ] [ i ] W[i]=W[i]+A[t]*C[n-t][i] W[i]=W[i]+A[t]C[nt][i]
然后是强化牌,强化牌肯定要先满足选满 k − 1 k-1 k1张那么转移的时候要用一下上面那个 H H H数组, R [ i ] R[i] R[i]表示考虑到当前牌时选 k − 1 k-1 k1张打以及 i i i张不打的方案数 W [ i ] = W [ i ] + R [ k − 1 ] ∗ B [ t ] ∗ C [ n − t ] [ i ] W[i]=W[i]+R[k-1]*B[t]*C[n-t][i] W[i]=W[i]+R[k1]B[t]C[nt][i]
第二部分的贡献也很好算,就是考虑剩下不打的牌中有多少张攻击牌,注意一下不要算 m − k m-k mk,因为这部分在第一种牌型中已经算过了: A n s 2 = ∑ i = 1 m − k − 1 W [ i ] ∗ R [ m − k − i ] Ans_2=\sum_{i=1}^{m-k-1}W[i]*R[m-k-i] Ans2=i=1mk1W[i]R[mki]
把两部分答案加起来就行了,这题就做完了;
(调了半天发现我先读的攻击牌再读的强化牌,吉老师你明明先介绍的攻击牌啊

#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
	char c = getchar();
	bool f = false;
	for (x = 0; !isdigit(c); c = getchar()) {
		if (c == '-') {
			f = true;
		}
	}
	for (; isdigit(c); c = getchar()) {
		x = x * 10 + c - '0';
	}
	if (f) {
		x = -x;
	}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
	read(x), read(y...);
}
const int MAX=3000,N=MAX+10,P=998244353;
int n,m,K,T;
LL A[N],B[N],C[N][N],F[N],G[N],H[N],W[N],R[N];
bool cmp(int a,int b) {
	return a>b;
}
//#define rua
int main() {
#ifdef rua
	freopen("GG.in","r",stdin);
#endif
	read(T);
	for(int i=0;i<=3000;++i) {
		C[i][0]=1; C[i][i]=1;
		for(int j=1;j<i;++j) {
			C[i][j]=C[i-1][j]+C[i-1][j-1];
			if(C[i][j]>=P) C[i][j]-=P;
		}
	}
	while(T--) {
		mem(F,0); mem(G,0); mem(H,0); mem(W,0); mem(R,0);
		int n,m,K; read(n,m,K);
		for(int i=1;i<=n;++i) read(B[i]);
		for(int i=1;i<=n;++i) read(A[i]);
		sort(A+1,A+n+1,cmp); sort(B+1,B+n+1,cmp);
		H[0]=1;
		for(int i=1;i<=n;++i) {
			for(int j=K;j;--j) {
				(G[j]+=(G[j-1]+1ll*A[i]*C[i-1][j-1]%P)%P)%=P;
				if(n-i>=m-K) (F[j]+=1ll*(G[j-1]+1ll*A[i]*C[i-1][j-1]%P)%P*C[n-i][m-K]%P)%=P;
				if(j==1) {
					for(int k=0;k<=m-K-1; ++k) (W[k]+=1ll*A[i]*C[n-i][k])%=P;
				}
				(H[j]+=(1ll*H[j-1]*B[i]%P))%=P;
				if(j==K-1) {
					for(int k=1;k<=m-K;++k) (R[k]+=1ll*H[j-1]*B[i]%P*C[n-i][k])%=P;
				}
			}
		}
		if(K==1) {
			for(int i=1;i<=m-K;++i) R[i]=C[n][i];
		}
		int res=0;
		for(int i=0;i<=m-K-1;++i) {
			int t=m-K-i;
			(res+=1ll*W[i]*R[t]%P)%=P;
		}
		for(int i=1;i<=K;++i) (res+=1ll*F[i]*H[K-i]%P)%=P;
		printf("%d\n",res);
	}	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值