zoukankan      html  css  js  c++  java
  • 【洛谷5299】[PKUWC2018] Slay the Spire(组合数学)

    点此看题面

    大致题意:(n)张强化牌(a_i)(n)张攻击牌(b_i),每张牌有一个权值(强化牌的权值大于(1)),每张强化牌能使所有攻击牌的权值乘上这张强化牌的权值,每张攻击牌造成的伤害等于这张攻击牌的权值。现在,以等概率抽出(m)张牌,并以最优策略使用其中至多(k)张牌造成最大的伤害。求所有情况下,造成伤害总和。

    前言

    感觉最近肝了好久的文化课,居然思维水平不但没降,还有了点提升?

    没想到居然能不看题解自己把这道题做出来,虽然(WA)了一次,但还算是有进步了吧!

    何为最优策略

    这道题看似毫无头绪,因此,我们先要好好推一推性质。

    假设现在我们已经选好了(m)张牌,那么最优策略是什么?

    首先,如果我们已经确定要用(x)张强化牌和(y)张攻击牌,那么根据贪心的想法,肯定是先使用权值最大的(x)张强化牌,再使用权值最大的(y)张攻击牌。

    因此我们把(a)(b)分别从大到小排序。

    同时,同样依据贪心,我们可以知道答案就是强化牌的乘积乘上攻击牌之和

    然后我们考虑,在什么情况下,把第(y)张攻击牌换成第(x+1)张强化牌,与原先答案相比不会变劣。

    原先答案是:

    [prod_{i=1}^xa_icdotsum_{i=1}^y b_i ]

    变化后的答案是:

    [prod_{i=1}^{x+1}a_icdotsum_{i=1}^{y-1}b_i ]

    则两式相减,即为答案的变化量,也就是:

    [(a_{x+1}-1)cdot(prod_{i=1}^xa_icdotsum_{i=1}^{y-1}b_i)-(prod_{i=1}^xa_i)cdot b_y ]

    当变化后答案不变劣,说明变化量(ge 0),即:

    [(a_{x+1}-1)cdot(prod_{i=1}^xa_icdotsum_{i=1}^{y-1}b_i)ge(prod_{i=1}^xa_i)cdot b_y ]

    我们把式子中的(prod_{i=1}^xa_i)去掉,就得到:

    [(a_{x+1}-1)cdot(sum_{i=1}^{y-1}b_i)ge b_y ]

    由于题目中说明,强化牌权值大于(1),所以(a_{x+1}-1ge 1)

    而在(y>1)时,因为(b)数组经过了从大到小排序,所以(sum_{i=1}^{y-1}b_i)肯定大于等于(b_y)

    所以我们可以发现,在(y>1)时,((a_{x+1}-1)cdot(sum_{i=1}^{y-1}b_i)ge b_y)是始终成立的。

    也就是说,在保有至少一张攻击牌的前提下,肯定是尽量选择强化牌会更优

    这么一来,这道题一下就可做得多了。

    预处理

    在正式开始解题之前,我们还需要进行预处理,定义几个变量。

    (f_{i,j,0})表示在前(i)张强化牌中选择(j)(i)张被选中的所有情况下,(j)张牌的乘积之和(g_{i,j,0})表示在前(i)张攻击牌中选择(j)(i)张被选中的所有情况下,(j)张牌的和之和

    同时,定义(f_{i,j,1}=sum_{x=1}^if_{x,j,0},g_{i,j,1}=sum_{x=1}^ig_{x,j,0})来辅助转移。

    则不难发现,有转移方程:

    [f_{i,j,0}=a_icdot f_{i-1,j-1,1} ]

    [f_{i,j,1}=f_{i,j,0}+f_{i-1,j,1} ]

    [g_{i,j,0}=b_icdot C_{i-1,j-1}+g_{i-1,j-1,1} ]

    [g_{i,j,1}=g_{i,j,0}+g_{i-1,j,1} ]

    注意,(g_{i,j,0})的转移中,(C_{i-1,j-1})表示在(i-1)个数中选择(j-1)个数的方案,即从(g_{i-1,j-1,1})转移到(g_{i,j,0})共有(C_{i-1,j-1})种情况,而每种情况卡牌权值和加上了(b_i),就相当于共加上了(b_icdot C_{i-1,j-1})

    至于这些东西究竟有什么用,待会儿你就会知道了。

    组合数学

    接下来,就是分类讨论+推式子啦。

    第一类:当(m)张牌中,强化牌的数量小于(k-1)张时。

    此时必然是选上所有的强化牌,然后选上权值最大的一些攻击牌。

    不难发现,其实等于(k-1)张时也符合这一类情况的操作方案,但为了方便起见,我们把等于(k-1)的情况放入另一类情况中中。

    对于这一种情况,我们枚举(i,j)分别表示强化牌有(i)最后被选中的攻击牌是第(j)

    在强化牌中选择(i)张的所有合法情况下的乘积之和,其实就是(f_{n,i,1})

    而强化牌中选择(i)张,攻击牌中就要选择(k-i)张,又由于最后被选中的攻击牌是第(j)张,所以所有合法情况下攻击牌的和之和,其实就是(g_{j,k-i,0})

    而若要最终选出的(k)张牌是这(k)张牌,就剩余(m-k)张牌就需要满足:

    • 不存在强化牌。
    • 所有攻击牌都必须在第(j)张之后,否则选这张攻击牌肯定比选第(j)张优。

    因此方案数就是(C_{n-j}^{m-k})

    总结一下,就是枚举(i,j),然后每次答案加上(f_{n,i,1}cdot g_{j,k-i,0}cdot C_{n-j}^{m-k})

    第二类:当(m)张强化牌中,强化牌的数量大于等于(k-1)时。

    此时必然是选上权值最大的(k-1)张强化牌,然后选上权值最大的一张攻击牌。

    对于这一种情况,我们枚举(i,j)分别表示最后被选中的强化牌是第(i)被选中的攻击牌是第(j)

    在前(i)张强化牌中选择(k-1)张,且第(i)张必选,所有合法情况下的乘积之和,其实就是(f_{i,k-1,0})

    选择第(j)张攻击牌,攻击牌之和其实就是第(j)张攻击牌的权值,也就是(b_j)

    而若要最终选出的(k)张牌是这(k)张牌,就剩余(m-k)张牌就需要满足:

    • 所有强化牌都必须在第(i)张之后。
    • 所有攻击牌都必须在第(j)张之后。

    因此方案数就是(C_{2n-i-j}^{m-k})

    总结一下,就是枚举(i,j),然后每次答案加上(f_{i,k-1,0}cdot b_jcdot C_{2n-i-j}^{m-k})

    具体实现可见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 6000
    #define X 998244353
    using namespace std;
    int n,m,k,a[N+5],b[N+5],C[N+5][N+5],f[N+5][N+5][2],g[N+5][N+5][2];
    I bool cmp(CI x,CI y) {return x>y;}
    int main()
    {
    	RI i,j;for(C[0][0]=i=1;i<=N;++i) for(C[i][0]=j=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%X;//预处理组合数
    	RI Tt,ans;scanf("%d",&Tt);W(Tt--)
    	{
    		for(scanf("%d%d%d",&n,&m,&k),ans=0,i=1;i<=n;++i) scanf("%d",a+i);
    		for(i=1;i<=n;++i) scanf("%d",b+i);sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);//从大到小排序
    		for(f[0][0][0]=f[0][0][1]=i=1;i<=n;++i) for(f[i][0][1]=j=1;j<=i;++j)//预处理f
    			f[i][j][0]=1LL*a[i]*f[i-1][j-1][1]%X,f[i][j][1]=(f[i][j][0]+f[i-1][j][1])%X;
    		for(i=1;i<=n;++i) for(j=1;j<=i;++j)//预处理g
    			g[i][j][0]=(1LL*b[i]*C[i-1][j-1]+g[i-1][j-1][1])%X,g[i][j][1]=(g[i][j][0]+g[i-1][j][1])%X;
    		for(i=0;i<k-1;++i) for(j=1;j<=n;++j) ans=(1LL*f[n][i][1]*g[j][k-i][0]%X*C[n-j][m-k]+ans)%X;//当强化牌数量小于k-1时
    		for(i=0;i<=n;++i) for(j=1;j<=n;++j) ans=(1LL*f[i][k-1][0]*b[j]%X*C[2*n-i-j][m-k]+ans)%X;//当强化拍数量大于等于k-1时
    		printf("%d
    ",ans);//输出答案
    	}return 0;
    }
    
  • 相关阅读:
    TCP链接异常断开后,对端仍然ESTABLISH
    mds0: Many clients (191) failing to respond to cache pressure
    Linux C/C++编译过程中的各种not declared in this scope
    Centos7添加磁盘并分区格式化
    Windows Terminal——安装并配置主题
    Kbone——创建项目报错 name can no longer contain capital letters
    Redis——大批量删除redis的key
    Redis——设置最大内存 | key淘汰机制
    Nightingale——滴滴夜莺部署【一】
    ELK——使用Docker快速搭建
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5299.html
Copyright © 2011-2022 走看看