【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\n",ans);
    }

    return 0;
}
posted @ 2018-06-24 10:39  Maxwei_wzj  阅读(126)  评论(0编辑  收藏  举报