「PKUWC2018」Slay the Spire

loj2538 「PKUWC 2018」Slay the Spire

对于这种题,感觉可以贪心

所以要先手玩,看看有没有最优决策

发现,如果k张,必定先打强化

关键的话是:“每个强化牌翻的倍数大于1”

意味着,多翻一倍少打一张攻击一定不劣。

如果m张,有i张是强化,选k个,

如果i<k,那么打i张强化,剩下打攻击

如果i>=k,打k-1张强化,1张攻击

发现,决策和分到的强化牌的数量有关系

从这方面dp

F(i,j)表示,分到i张强化,打出j张,所有的翻的倍率(卡牌乘积)的和

G(i,j)表示,分到i张攻击,打出j张,所有方案的(原始)伤害的和

那么答案就是:F(i,i)*G(m-i,k-i)和F(i,k-1)*G(m-i,1)(乘法分配律)

考虑怎么求F,G

有了一些牌,一定打最大的一些个

所以sort

然后,f[i][j]表示,前i个以i结尾,选择了j个强化牌所翻的倍率的和

dp,前缀和优化即可。

g[i][j]表示,前i个以i结尾,选择了j个攻击牌所有方案的伤害的和

同上处理

然后枚举强化牌i张,

F,G只需要计算n次,所以不用预处理。每次暴力求,枚举最后一个选择的是谁,剩下位置组合数来搞。

注意,f[i][0]是C(n,i),象征选择的方案数,因为乘法的单位元是1,不能没有。

没有强化牌认为翻1倍。

而g[i][0]就是0啦。

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
#define int long long
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=3003;
const int mod=998244353;
int f[N][N],g[N][N],c[N][N];
int s[N],t[N];
int a[N],b[N];
int n,m,k;
void clear(){
    
}
bool cmp(int x,int y){
    return x>y;
}
int F(int x,int y){
    if(x<y) return 0;
    //if(x==0&&y==0) return 1;
    if(!y) return c[n][x];
    int ret=0;
    for(reg i=y;i<=n;++i){
        ret=(ret+(ll)f[i][y]*c[n-i][x-y])%mod;
    }
    return ret;
}
int G(int x,int y){
    if(x<y) return 0;
    //if(x==0||y==0) return 0;
    int ret=0;
    for(reg i=y;i<=n;++i){
        ret=(ret+(ll)g[i][y]*c[n-i][x-y])%mod;
    }
    return ret;
}
int main(){
    int T;
    //while(1);
    rd(T);
    c[0][0]=1;
    for(reg i=1;i<=3000;++i){
        c[i][0]=1;
        for(reg j=1;j<=i;++j){
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }
    }
    while(T--){
        rd(n);rd(m);rd(k);
        clear();
        for(reg i=1;i<=n;++i) rd(a[i]);
        for(reg j=1;j<=n;++j) rd(b[j]);
        sort(a+1,a+n+1,cmp);sort(b+1,b+n+1,cmp);
        
        memset(s,0,sizeof s);
        memset(t,0,sizeof t);
        t[0]=1;
        s[0]=1;
        for(reg i=1;i<=n;++i){
            for(reg j=i;j>=1;--j){
                f[i][j]=(ll)a[i]*s[j-1]%mod;    
            }
            for(reg j=i;j>=1;--j){
                s[j]=(s[j]+f[i][j])%mod;
            }
        }
        memset(s,0,sizeof s);
        memset(t,0,sizeof t);
        t[0]=1;
        for(reg i=1;i<=n;++i){
            for(reg j=i;j>=1;--j){
                g[i][j]=(s[j-1]+(ll)b[i]*c[i-1][j-1])%mod;
            }
            for(reg j=i;j>=1;--j){
                (t[j]+=t[j-1])%=mod;
                (s[j]+=g[i][j])%=mod;
            }
        }
//        for(reg i=1;i<=n;++i){
//            cout<<" ii "<<a[i]<<endl;
//            for(reg j=1;j<=i;++j){
//                cout<<" i j "<<i<<" "<<j<<" ff "<<f[i][j]<<" gg "<<g[i][j]<<endl;
//            }
//        }
        ll ans=0;
        for(reg i=0;i<m;++i){
            if(i<k){
                ans=(ans+(ll)F(i,i)*G(m-i,k-i)%mod)%mod;
            }else{
                ans=(ans+(ll)F(i,k-1)*G(m-i,1)%mod)%mod;
            }
        }
        printf("%lld
",ans);
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2019/1/9 7:21:30
*/

手玩出规律

列出状态

然后乘法分配律得到答案

然后求F,G

原文地址:https://www.cnblogs.com/Miracevin/p/10242438.html