zoukankan      html  css  js  c++  java
  • loj #2538. 「PKUWC2018」Slay the Spire

    $ color{#0066ff}{ 题目描述 }$

    九条可怜在玩一个很好玩的策略游戏:Slay the Spire,一开始九条可怜的卡组里有 (2n) 张牌,每张牌上都写着一个数字(w_i),一共有两种类型的牌,每种类型各 (n) 张:

    1. 攻击牌:打出后对对方造成等于牌上的数字的伤害。
    2. 强化牌:打出后,假设该强化牌上的数字为(x),则其他剩下的攻击牌的数字都会乘上 (x)保证强化牌上的数字都大于 1

    现在九条可怜会等概率随机从卡组中抽出 (m) 张牌,由于费用限制,九条可怜最多打出 (k) 张牌,假设九条可怜永远都会采取能造成最多伤害的策略,求她期望造成多少伤害。

    假设答案为 ( ext{ans}),你只需要输出

    (left ( ext{ans} imes frac{(2n)!}{m!(2n-m)!} ight) ~mod 998244353)

    即可

    其中 (x!) 表示 (prod_{i=1}^{x}i),特别地,(0!=1)

    (color{#0066ff}{输入格式})

    第一行一个正整数 (T) 表示数据组数

    接下来对于每组数据:

    第一行三个正整数 (n,m,k)

    第二行 (n) 个正整数 (w_i),表示每张强化牌上的数值。

    第三行 (n) 个正整数 (w_i),表示每张攻击牌上的数值。

    (color{#0066ff}{输出格式})

    输出 (T) 行,每行一个非负整数表示每组数据的答案。

    (color{#0066ff}{输入样例})

    2
    2 3 2
    2 3
    1 2
    10 16 14
    2 3 4 5 6 7 8 9 10 11
    1 2 3 4 5 6 7 8 9 10
    

    (color{#0066ff}{输出样例})

    19
    253973805
    

    (color{#0066ff}{数据范围与提示})

    对于所有数据,有 (1leq kleq mleq 2nleq 3 imes 10^3),且(1leq w_ileq 10^8)

    保证强化牌上的数字都大于 1

    以下 ((sum 2n)) 表示对于输入中所有数据的(2n)的和。

    对于 (10\%) 的数据,有 (1leq sum 2nleq 10)

    对于 (20\%) 的数据,有 (1leq sum 2nleq 100)

    对于 (30\%) 的数据,有 (1leq sum 2nleq 500)

    另有 (20\%) 的数据,满足所有攻击牌的数值相同。

    另有 (20\%) 的数据,满足 (m=k)

    对于 (100\%) 的数据,有 (1leq sum 2nleq 30000)

    (color{#0066ff}{题解})

    假如说我们已经有了m张牌,现在考虑怎么打出k张牌会最优呢

    首先,一定是先打强化牌再打攻击牌最优,不解释

    那么到底选多少攻击牌和强化牌呢

    首先,我们先把m张牌分两类从大到小排序,那么肯定是选两类牌的一个前缀

    比如,强化3 2, 攻击a b,(k=3)

    那么显然(2*3*a=3a+3a>3a+3b)

    因此,我们要尽可能多的选强化牌,剩下的选攻击牌

    我们设状态(f[i][j][0/1])表示强化牌中,前i张选j张,第i张选不选(这是为了统计方案不重不漏)的贡献

    举个锤子,比如强化牌5 4 3 2,那么(f[4][3][1])就是(2 * 3 * 5+2 * 4 * 5+2 * 3 * 4),就是所有合法贡献的和

    同理(g[i][j][0/1])是针对ATK的DP

    转移很容易,(O(n^2))

    f[0][0][0] = 1;	
    for(int i = 1; i <= n; i++) {
    	for(int j = 0; j <= i; j++) {
    		if(j >= 1) {
    			f[i][j][1] = ((f[i - 1][j - 1][1] + f[i - 1][j - 1][0]) % mod * STG[i] % mod);
    			g[i][j][1] = ((g[i - 1][j - 1][1] + g[i - 1][j - 1][0]) % mod + ATK[i] * C(i - 1, j - 1) % mod) % mod;
    		}
    		f[i][j][0] = (f[i - 1][j][0] + f[i - 1][j][1]);
    		g[i][j][0] = (g[i - 1][j][0] + g[i - 1][j][1]);
    	}
    }
    

    之后我们开始统计方案

    为了不重不漏,我们分别在两个序列枚举端点i和j

    首先考虑已有的m张牌中,强化牌(ge k-1)

    那么我们肯定是选前(k-1)张大的强化牌和1张攻击牌, 剩下的(m-k)放在其它位置

    (f[i][k-1][1]*g[j][1][1]*C_{2n -i-j}^{m-k})

    如果没有那么多,只只有(w,w<k-1)张,我们肯定都选,然后剩下的(k-w)张是攻击牌,注意,这时候剩下的(m-k)张牌必须只能是攻击牌, 因为强化牌不够!

    (f[i][w][1]*g[j][k-w][1]*C_{n-j}^{m-k})

    注意当不选强化牌的时候要特判一下,即(k=1)的时候,还有(k e 1)时不选强化牌的情况,直接统计方案的话因为f是0,所以最后就是0,显然不对了,单独算一下就行

    #include<bits/stdc++.h>
    #define LL long long
    LL in() {
    	char ch; LL x = 0, f = 1;
    	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    	return x * f;
    }
    const int mod = 998244353;
    const int maxn = 3030;
    LL f[maxn][maxn][2], g[maxn][maxn][2];
    LL ATK[maxn], STG[maxn], fac[maxn << 1], inv[maxn << 1];
    int n, m, k;
    LL ksm(LL x, LL y) {
    	LL re = 1LL;
    	while(y) {
    		if(y & 1) re = re * x % mod;
    		x = x * x % mod;
    		y >>= 1;
    	}
    	return re;
    }
    LL C(int x, int y) { 
    	if(x < y) return 0;
    	return fac[x] * inv[y] % mod * inv[x - y] % mod; 
    }
    void predoit() {
    	std::sort(STG + 1, STG + n + 1, std::greater<LL>());
    	std::sort(ATK + 1, ATK + n + 1, std::greater<LL>());
    	f[0][0][0] = 1; 
    	for(int i = 1; i <= n; i++) {
    		for(int j = 0; j <= i; j++) {
    			if(j >= 1) {
    				f[i][j][1] = ((f[i - 1][j - 1][1] + f[i - 1][j - 1][0]) % mod * STG[i] % mod);
    				g[i][j][1] = ((g[i - 1][j - 1][1] + g[i - 1][j - 1][0]) % mod + ATK[i] * C(i - 1, j - 1) % mod) % mod;
    			}
    			f[i][j][0] = (f[i - 1][j][0] + f[i - 1][j][1]);
    			g[i][j][0] = (g[i - 1][j][0] + g[i - 1][j][1]);
    		}
    	}
    }
    void fuck() {
    	LL ans = 0;
    	for(int i = k - 1; i <= n; i++) 
    		for(int j = 1; j <= n; j++)
    			(ans += f[i][k - 1][1] * g[j][1][1] % mod * C((n << 1) - i - j, m - k) % mod) %= mod;
    	for(int w = 1; w <= k - 2; w++) {
    		LL tot = 0;
    		for(int i = 1; i <= n; i++) (tot += f[i][w][1]) %= mod;
    		for(int i = 1; i <= n; i++) (ans += tot * g[i][k - w][1] % mod * C(n - i, m - k) % mod) %= mod;
    	}
    	for(int i = 1; i <= n; i++) (ans += g[i][k][1] * C(n - i, m - k) % mod) %= mod;
    	printf("%lld
    ", ans);
    }
    void work() {
    	LL ans = 0;
    	for(int i = 1; i <= n; i++) (ans += ATK[i] * C((n << 1) - i, m - k) % mod) %= mod;
    	printf("%lld
    ", ans);
    }
    
    int main() {
    	fac[0] = 1;
    	for(LL i = 1; i <= 6000; i++) fac[i] = i * fac[i - 1] % mod;
    	inv[6000] = ksm(fac[6000], mod - 2);
    	for(LL i = 5999; i >= 0; i--) inv[i] = (i + 1) * inv[i + 1] % mod;
    	for(int T = in(); T --> 0;) {
    		n = in(), m = in(), k = in();
    		for(int i = 1; i <= n; i++) STG[i] = in();
    		for(int i = 1; i <= n; i++) ATK[i] = in();
    		predoit();
    		if(k == 1) work();
    		else fuck();
    	}
    	return 0;
    }
    
  • 相关阅读:
    c#泛型的使用
    关于Asp.net无法写入输出文件的原因
    利用OLEDB导出数据到Excel
    中秋祝福
    C#获取当前域用户名
    【程序员必读】骨灰级程序员20条编程经验
    SQL SERVER 2005无法远程连接的问题
    ASP.Net 实现伪静态方法及意义
    js+ajax获取文件大小
    C#遍历指定文件夹中的所有文件
  • 原文地址:https://www.cnblogs.com/olinr/p/10518444.html
Copyright © 2011-2022 走看看