2022 International Collegiate Programming Contest, Jinan Site J. Skills(线性dp 根号思想 松弛思想)

题目

t(t<=10000)组样例,

给定n(n<=1e3)天,有三种技能,三种技能的熟练度初始都为0,

第i天学第j种技能时,会令第j种技能的熟练度提高aij(0<=aij<=1e4)

第i天只能学一种技能,剩下两种技能没有学,

对于没有学的技能k,假设算上第i天后已经是连续第x天没有学技能k了,

则第k种技能的熟练度会减少x,熟练度下降的过程中,不会小于0

求n天之后,三种技能的熟练度之和的最大值

思路来源

官方题解

题解

首先,用根号的思想,每根号w天不学,就会下降超过一天学的收益,不如期间用一天学一下

然后,用dp的松弛思想,即,合理地改变规则,使得最优解还能被枚举到

最优解中,一个课程要么没学,要么学了之后,中间不会降到0以下

而降到0以下是不会影响到最优解的转移的,所以直接扣除即可

但是,其实最优解可能只学了一门或两门课程,所以状态中还需要标明一门课学没学

在代码实现中,dp[i][j][k][l]表示到了第i天时,第i天当天学的是第j门课,

对于没学的两门课来说,第一门课已经没学k天,第二门课已经没学l天时,最大的熟练度之和

特别地,当k=0、l=0时,代表这门课从来没有学过,一直为0,转移的时候不扣除天数

滚动掉第一维后,就只剩dp[2][3][205][205]了

预处理了一下,id[i][0]表示学课程i时没学的第一门课的标号,id[i][1]表示第二门课的标号

如果当天学的j,没学的第一门课连续没学k天,没学的第二门课连续没学l天,状态dp[cur][j][k][l]

转移时,枚举下一天学的是m,需要判断一下与m对应的没学的第一/二门课连续没学的天数

int vf=(nf==j?1:(nf==f?k+(k>0):l+(l>0)));

写成if else可能会比较好理解,即,

1. 如果第一门课是j的话,当前连续没学一天,

2. 是之前没学的第一门课,

①如果这门课没学过,即k=0,就还是k=0

②否则,连续没学k+1天

3. 是之前没学的第二门课,

①如果这门课没学过,即l=0,就还是l=0

②否则,连续没学l+1天

用最后一天的结果,更新答案取最大即可

代码

#include<bits/stdc++.h>
//#include<iostream>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pb push_back
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
const int N=1e3+10,M=200,W=205,mod=998244353;
int t,n,a[N][3],dp[2][3][W][W],id[3][2];
void upd(int &x,int y){
    x=max(x,y);
}
int main(){
    //freopen("jinan.in","r",stdin);
    //freopen("jinan.out","w",stdout);
    rep(j,0,2){
        int c=0;
        rep(k,0,2){
            if(j==k)continue;
            id[j][c++]=k;
        }
    }
    sci(t);
    while(t--){
        sci(n);
        rep(i,0,n-1){
            rep(j,0,2){
                sci(a[i][j]);
            }
        }
        memset(dp,128,sizeof dp);
        rep(j,0,2){
            rep(k,0,1){
                rep(l,0,1){
                    dp[0][j][k][l]=a[0][j]-k-l;
                }
            }
        }
        rep(i,0,n-2){
            int cur=i&1,nex=cur^1;
            memset(dp[nex],128,sizeof dp[nex]);
            rep(j,0,2){
                int f=id[j][0],s=id[j][1];
                rep(k,0,M){
                    rep(l,0,M){
                        //printf("i:%d j:%d k:%d l:%d dp:%d\n",i,j,k,l,dp[cur][j][k][l]);
                        rep(m,0,2){
                            int nf=id[m][0],ns=id[m][1];
                            int vf=(nf==j?1:(nf==f?k+(k>0):l+(l>0)));
                            int vs=(ns==j?1:(ns==f?k+(k>0):l+(l>0)));
                            upd(dp[nex][m][vf][vs],dp[cur][j][k][l]+a[i+1][m]-vf-vs);
                        }
                    }
                }
            }
        }
        int ans=0;
        rep(j,0,2){
            rep(k,0,M){
                rep(l,0,M){
                    upd(ans,dp[(n-1)&1][j][k][l]);
                }
            }
        }
        pte(ans);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值