Round 1A 2022 - Code Jam 2022 C.Weightlifting(区间dp)

题目

时限20s,T(T<=100)组样例,共E(E<=100)次锻炼,每次锻炼可能需要W(W<=100)种杠铃,

第i天第j种所需要的杠铃片数为Xij(0<=Xij<=100),

杠铃片放到一个形如「栈」的杠铃架上,

Round 1A 2022 - Code Jam 2022 C.Weightlifting(区间dp)_第1张图片

比如,第一天是AABA,第二天是AACCDD,

则需要第一天放入AABA,弹掉A,弹掉B,

再放入C,放入C,放入D,放入D,才能得到第二天的杠铃片

最后求杠铃架从空的初始状态,锻炼完E次后,再保持空状态,

所操作的杠铃片的最小数量是多少

思路来源

官方题解

jiangly代码

题解

注意到,如果第[1,n]次锻炼内有一些杠铃片是公共的,

则这些杠铃片可以直接垫在底下,最开始放入,最后取出,然后忽略这些杠铃片

然后考虑分治,第[l,r]次锻炼的答案可以由[l,x]和[x+1,n]求得,

设第[l,r]次锻炼公共的杠铃片为mn[l][r],

则第[l,r]次锻炼,可以拆解成,

一开始在栈内垫入mn[l][x]个杠铃片,进行第[l,x]次锻炼,完成后弹栈把杠铃片删成mn[l][r]个,

再垫杠铃片使栈内杠铃片数为mn[x+1][r]个,进行第[x+1,r]次锻炼,完成后把杠铃片删完

一开始初始化长度为1的区间,其贡献为2*mn[i][i],表示杠铃片的变化为0->mn[i][i]->0

则区间合并的时候,合并前杠铃片的变化是0->mn[l][x]->0->mn[x+1][r]->0,

合并后,杠铃片的变化为0->mn[l][x]->mn[l][r]->mn[x+1][r]->0,即发生合并时,答案减去2*mn[l][r]

心得

想到这个公共杠铃片的trick了,但还是最终没能想到区间dp

看了看自己做过的题,发现和hdu4283 You Are the One(区间DP)这个题很像

题解是按jiangly代码写的,官方题解与之大同小异。

官方题解

dp[l][r]表示把第[l,r]次锻炼公有的杠铃片(其个数记为C[l,r])事先加载进栈,锻炼完第[l,r]次后,弹栈使得栈内最终只保留这C[l,r]个杠铃片的最小操作次数。

转移考虑枚举一个中间次数x,dp[l][r]=min(dp[l][r],dp[l][x]+dp[x+1][r]+2*((C[l,x]-C[l,r])+(C[x+1,r]-C[l,r]))),表示一个拆解为子问题的过程。

要锻炼[l,r]这些次,栈中现在有公有杠铃片C[l,r]个,先入栈使栈内杠铃片变成C(l,x)个,再进行第[l,x]次锻炼,再弹栈让杠铃片恢复为C[l,r]个,再入栈使杠铃片变为C[x+1,r]个,再进行第[x+1,r]次锻炼,再弹栈让杠铃片恢复为C[l,r]个。

最后答案为dp[1][n]+2*C[1][n]。

或者引入空元素0、n+1,最后答案为dp[0][n+1]。

代码

#include
using namespace std;
const int N=105,INF=0x3f3f3f3f;
int t,n,m,dp[N][N],a[N][N],mn[N][N],now[N];
int main(){
    scanf("%d",&t);
    for(int ca=1;ca<=t;++ca){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                scanf("%d",&a[i][j]);
            }
        }
        memset(mn,0,sizeof mn);
        for(int l=1;l<=n;++l){
            memset(now,INF,sizeof now);
            for(int r=l;r<=n;++r){
                for(int j=1;j<=m;++j){
                    now[j]=min(now[j],a[r][j]);
                    mn[l][r]+=now[j];
                }
            }
        }
        for(int i=1;i<=n;++i){
            dp[i][i]=2*mn[i][i];
        }
        for(int len=2;len<=n;++len){
            for(int l=1;l+len-1<=n;++l){
                int r=l+len-1;
                dp[l][r]=INF;
                for(int k=l;k+1<=r;++k){
                    dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]-2*mn[l][r]);
                }
            }
        }
        printf("Case #%d: %d\n",ca,dp[1][n]);
    }
    return 0;
}

你可能感兴趣的:(线上比赛,#,区间dp,google,codejam,区间dp)