zoukankan      html  css  js  c++  java
  • 【LOJ2538】Slay the Spire(PKUWC2018)-DP+组合数

    测试地址:Slay the Spire
    做法:本题需要用到DP+组合数。
    题目要求的就是对于所有抽牌的方案,能得到的伤害值的总和。
    我们知道,在使用的强化牌一定的基础上,攻击牌肯定是从大到小取最优。然后要观察到一个结论:如果有强化牌,先出强化牌是最优的,出到没有强化牌,或者只有一张牌可出的时候再出攻击牌。如何证明?因为强化牌的倍数k>1,令出强化牌之前的可出攻击牌数值和为s,最小的可出攻击牌数值为x,因为要多出一张强化牌,所以x就不能出了,那么多出强化牌比不出能打出的伤害要多k(sx)s,即(k1)(sx)x,因为k>1sx>x(因为x是最小值),所以多出强化牌总是最优的,得证。
    于是我们就自然而然有了一个状态定义,令G(x,y)为在所有x张强化牌的方案中取最大y张能得到的倍数之和,F(x,y)为在所有x张攻击牌的方案中取最大y张能得到的伤害之和,有:
    ans=i=0k1G(i,i)F(mi,ki)+i=km1G(i,k1)F(mi,1)
    问题就是如何求FG
    以强化牌为例,我们枚举最后出的牌中数值最小的那一张,所以我们先将牌从大到小排序,然后令g(i,j)为前i张强化牌中取j张,且第i张必须取,能得到的倍数之和,有:
    g(i,j)=wip=1i1g(p,j1)
    用前缀和即可优化到O(n2)。那么有:
    G(x,y)=i=1ng(i,y)Cnixy
    对于攻击牌也差不多,也是从大到小排序,然后令f(i,j)为前i张攻击牌中取j张,且第i张必须取,能得到的伤害之和,有:
    f(i,j)=wiCi1j1+p=1i1f(p,j1)
    同样用前缀和优化到O(n2)。那么有:
    F(x,y)=i=1nf(i,y)Cnixy
    于是在计算ans时,我们要求O(n)FG,每次求是O(n),而预处理组合数的时间复杂度是O(n2)的,所以总的时间复杂度是O(n2),可以通过此题。
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll mod=998244353;
    int T,n,m,k,last=0;
    ll wg[1510],wf[1510],C[1510][1510]={0};
    ll g[1510][1510],f[1510][1510],sum[1510][1510],sumf[1510][1510];
    
    bool cmp(ll a,ll b)
    {
        return a>b;
    }
    
    void calc_C(int n)
    {
        if (n<=last) return;
        for(int i=last+1;i<=n;i++)
        {
            C[i][0]=1;
            for(int j=1;j<=i;j++)
                C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        }
        last=n;
    }
    
    ll G(int x,int y)
    {
        if (x>n||x<y) return 0;
        if (y==0) return C[n][x];
        ll ans=0;
        for(int i=1;i<=n;i++)
            ans=(ans+g[i][y]*C[n-i][x-y])%mod;
        return ans;
    }
    
    ll F(int x,int y)
    {
        if (x>n||x<y) return 0;
        ll ans=0;
        for(int i=1;i<=n;i++)
            ans=(ans+f[i][y]*C[n-i][x-y])%mod;
        return ans;
    }
    
    int main()
    {
        scanf("%d",&T);
        C[0][0]=1;
        while(T--)
        {
            scanf("%d%d%d",&n,&m,&k);
            calc_C(n);
            for(int i=1;i<=n;i++)
                scanf("%lld",&wg[i]);
            for(int i=1;i<=n;i++)
                scanf("%lld",&wf[i]);
    
            sort(wg+1,wg+n+1,cmp);
            sort(wf+1,wf+n+1,cmp);
            for(int i=0;i<=n+1;i++)
                g[0][i]=sum[0][i]=0;
            g[0][0]=sum[0][0]=1;
            for(int i=1;i<=n;i++)
            {
                g[i][0]=sum[i][0]=1;
                for(int j=1;j<=i;j++)
                {
                    if (j>k) break;
                    g[i][j]=wg[i]*sum[i-1][j-1]%mod;
                    sum[i][j]=(sum[i-1][j]+g[i][j])%mod;
                }
                sum[i][i+1]=0;
            }
    
            for(int i=0;i<=n+1;i++)
                f[0][i]=sumf[0][i]=0;
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<=i;j++)
                {
                    if (j>k) break;
                    f[i][j]=(sumf[i-1][j-1]+wf[i]*C[i-1][j-1])%mod;
                    sumf[i][j]=(sumf[i-1][j]+f[i][j])%mod;
                }
                sumf[i][i+1]=0;
            }
    
            ll ans=0;
            for(int i=0;i<=k-1;i++)
                ans=(ans+G(i,i)*F(m-i,k-i))%mod;
            for(int i=k;i<=m-1;i++)
                ans=(ans+G(i,k-1)*F(m-i,1))%mod;
            printf("%lld
    ",ans);
        }
    
        return 0;
    }
  • 相关阅读:
    CentOS 7.4 发布下载,安全稳定的Linux发行版
    PHP缓存机制详解
    用FastDFS一步步搭建文件管理系统
    linux中mv命令使用详解
    linux grep命令详解
    音频放大器的设计
    C#学习笔记(九)——集合、比较和转换
    Kinect学习笔记(五)——更专业的深度图
    C#学习笔记(八)——定义类的成员
    kinect学习笔记(四)——各种数据流
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793312.html
Copyright © 2011-2022 走看看