题目分类:常规DP题,是DP各维表示的状态设对,转移方程就可以直接出的那种
思路:看到题目里面有m个煤炭,有n个铁矿,求最小时间
发现没有魔法的时候,就是一个经典的01背包问题
设dp[i][j]=烧了i个煤炭,获得了j个铁矿时所用的最小时间
转移方程有:dp[i][j]=min(dp[i-1][j](不用第i个煤炭),dp[i-1][j-x[i]]+y[i](用第i个煤炭)
最终答案为dp[n][m]
但是这一题有一个最多释放一次的魔法
所以考虑在原先的dp基础上再开一维
设dp[i][j][k]=烧了i个煤炭,获得了j个铁矿,使用了k次魔法时所用的最小时间
发现k=0时转移方程和之前一样:dp[i][j][0]=min(dp[i-1][j][0](不用第i个煤炭),dp[i-1][j-x[i]][0]+y[i](用第i个煤炭)
k=1时,转移方程也类似:dp[i][j][1]=min(dp[i-1][j][1](不用第i个煤炭),dp[i-1][j-x[i]][1]+y[i](用第i个煤炭,dp[i-1][j-2*x[i]][0]+(y[i]/2)(对第i个煤炭施加魔法并用它)
最终答案为dp[n][m][1],不用min(dp[n][m][0],dp[n][m][1])(虽然是一样的),因为用魔法比不用魔法烧完铁矿的时间一定短
注意数组边界问题就行
代码1(开快读在100ms左右)
#define int long long
 
int x[M],y[M];
 
void InfiniteLight() {
    int n,m;
    read(n);
    read(m);
    vector<vector<vector<int>>>dp(n+1, vector<vector<int>>(m+1, vector<int>(2,1e18)));
    Forl(i,1,n){
        read(x[i]);
        read(y[i]);
    }
    Forl(i,0,n){
        dp[i][0][0]=dp[i][0][1]=0;
    }
    Forl(i,1,n){
        Forr(j,m,1){
            dp[i][j][0]=min(dp[i-1][max(j-x[i],0LL)][0]+y[i],dp[i-1][j][0]);
            dp[i][j][1]=min({dp[i-1][j][1],dp[i-1][max(j-2*x[i],0LL)][0]+(y[i]/2LL),dp[i-1][max(j-x[i],0LL)][1]+y[i]});
        }
    }
    println(dp[n][m][1]);
    return ;
}
当然,可以发现,由于i的状态只和i-1有关,所以可以使用滚动数组优化掉第一维
代码2(开快读在10ms左右)
#define int long long
 
int x[M],y[M];
 
void InfiniteLight() {
    int n,m;
    read(n);
    read(m);
    vector<vector<int>>dp(m+1,vector<int>(2,1e18));
    Forl(i,1,n){
        read(x[i]);
        read(y[i]);
    }
    dp[0][0]=dp[0][1]=0;
    Forl(i,1,n){
        Forr(j,m,1){
            dp[j][0]=min(dp[max(j-x[i],0LL)][0]+y[i],dp[j][0]);
            dp[j][1]=min({dp[j][1],dp[max(j-2*x[i],0LL)][0]+(y[i]/2LL),dp[max(j-x[i],0LL)][1]+y[i]});
        }
    }
    println(dp[m][1]);
    return ;
}