[BZOJ 2964] Boss单挑战

Link:https://www.lydsy.com/JudgeOnline/problem.php?id=2964

Algorithm:

一道很新颖的背包问题

此题每个状态要维护的量巨多,而转移方式也巨多,直接写转移方程明显是不现实的

那要考虑的就是能否将这些量分开考虑,它们间是否有明确的约束关系

可以发现mp和sp的相关操作和在哪一个回合无关,而只与需要的回合总数有关

那么就先DP出fm[i]和fs[i],分别表示用i个回合能用mp与sp打出的最大伤害,可以算出最少要mneed个回合战胜boss

接下来再逐个回合考虑hp问题,用dp[i][j]表示到第i个回合血量为j时能挤出的最大回合数

能挤出mneed个回合则YES,能在第n回合保持不死则输出Tie,否则NO

Code:

#include <bits/stdc++.h>

using namespace std;

const int MAXN=1000+10;
const int INF=1<<27;

int T,n,m,x,hp,mp,sp,mp_cnt,sp_cnt,dhp,dsp,dmp;
int a[MAXN],ump[MAXN],amp[MAXN],usp[MAXN],asp[MAXN];
int fm[MAXN],gm[MAXN][MAXN],fs[MAXN],gs[MAXN][MAXN],dp[MAXN][MAXN];

void up(int &x,int y){if(x<y) x=y;}

void solve()
{
    memset(fm,0,sizeof(fm));memset(gm,0,sizeof(gm));
    memset(fs,0,sizeof(fs));memset(gs,0,sizeof(gs));
    
    
    cin>>n>>m>>hp>>mp>>sp>>dhp>>dmp>>dsp>>x;
    for(int i=1;i<=n;i++) cin >> a[i];
    cin >> mp_cnt;
    for(int i=1;i<=mp_cnt;i++) cin >> ump[i] >> amp[i];
    cin >> sp_cnt;
    for(int i=1;i<=sp_cnt;i++) cin >> usp[i] >> asp[i];
    
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=mp;j++) up(fm[i],gm[i][j]);
        if(i==n) break;
        for(int j=0;j<=mp;j++)
        {
            up(gm[i+1][min(mp,j+dmp)],gm[i][j]);
            for(int k=1;k<=mp_cnt;k++)
                if(j>=ump[k]) up(gm[i+1][j-ump[k]],gm[i][j]+amp[k]);
        }
    }
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=sp;j++) up(fs[i],gs[i][j]);
        if(i==n) break;
        for(int j=0;j<=sp;j++)
        {
            up(gs[i+1][min(sp,j+dsp)],gs[i][j]+x);
            for(int k=1;k<=sp_cnt;k++)
                if(j>=usp[k]) up(gs[i+1][j-usp[k]],gs[i][j]+asp[k]);
        }
    }
    
    int mneed=INF;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
            if(fm[i]+fs[j]>=m) mneed=min(mneed,i+j);
    
    for(int i=0;i<MAXN;i++)
        for(int j=0;j<MAXN;j++)
            dp[i][j]=-INF;
            
    dp[1][hp]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=hp;j++)
            if(dp[i][j]>=mneed)
            {
                cout << "Yes " << i << endl;
                return;
            }
        for(int j=1;j<=hp;j++)
        {
            if(min(hp,j+dhp)>a[i]) up(dp[i+1][min(hp,j+dhp)-a[i]],dp[i][j]);
            if(j-a[i]>0) up(dp[i+1][j-a[i]],dp[i][j]+1);
        }
    }
    
    for(int i=1;i<=hp;i++)
        if(dp[n+1][i]>=0)
        {
            cout << "Tie" << endl;
            return;
        }
    cout << "No" << endl;
}

int main()
{
    cin >> T;
    while(T--) solve();
    return 0;
}

Review:

发现转移状态时要维护的信息过多时,可以查看这些量是否可以分离

先逐个转移,最后再根据较弱的约束将状态加合

注意大小写啊!!

猜你喜欢

转载自www.cnblogs.com/newera/p/9050337.html