2024牛客暑假多校训练营2——两道入门DP(C题,I题)选讲

C.Red Walking on Grid

https://ac.nowcoder.com/acm/contest/81597/C

数组定义: d p [ i ] [ j ] dp[i][j] dp[i][j] —— 在 i i i j j j 列这个位置的走过的R的个数

状态方程: 1. d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + 1 1.dp[i][j] = dp[i][j - 1] + 1 1.dp[i][j]=dp[i][j1]+1(如果同行能走)

​ $ 2. d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − 1 ] + 1 ) 2.dp[i][j] = max(dp[i][j],dp[i - 1][j-1] + 1) 2.dp[i][j]=max(dp[i][j],dp[i1][j1]+1)(如果同列能走)(注意!!在两行进行这个决策时,后面那个 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1] 应该放的是没有经过方程 2 2 2 的原始值)

思路:我们从左往右(外循环),从上往下(内循环)遍历,就能避免走过这个格子又经过的情况。如果同行能走(两个格子上都是"R"),则先进行方程 1 1 1 ,因为我们实行贪心策略能走则走。如果这一列能走,则我们取MAX,看是”从左边走过来的值大“还是”从右边走过来的值大“,我们取原始值进行比较就是因为不能在这一行里往返走。

 void solve() {
    //code here
    int n;cin >> n;
   
    string str[2];
    cin >> str[0] >> str[1];
    str[0] = 'W' + str[0] + 'W';			//为了方便防止越界
    str[1] = 'W' + str[1] + 'W';
    n += 2;
 
    vector<vector<int>>dp(2,vector<int>(n));
 
    for(int i = 1;i < n - 1;++i){
        if(str[0][i] == 'R')dp[0][i] = dp[0][i - 1] + 1;
        if(str[1][i] == 'R')dp[1][i] = dp[1][i - 1] + 1;
        if(str[0][i] == str[1][i] && str[0][i] == 'R'){
            int t1 = dp[0][i],t2 = dp[1][i];
            dp[0][i] = max(dp[0][i],t2 + 1);
            dp[1][i] = max(dp[1][i],t1 + 1);
        }
    }
  
    int ans = 0;
    for(int i = 1;i < n - 1;++i){
        ans = max({ans,dp[0][i] - 1,dp[1][i] - 1});
    }
    cout << ans << endl;
}

I.Red Playing Cards

https://ac.nowcoder.com/acm/contest/81597/I

数组定义: d p [ i ] dp[i] dp[i] —— 合并完 i i i 位置这个值所对应的区间 [ p o s [ a r r [ i ] ] . x , p o s [ a r r [ i ] ] . y ] [pos[arr[i]].x,pos[arr[i]].y] [pos[arr[i]].x,pos[arr[i]].y] 其中 p o s [ a r r [ i ] ] . y 即为 i 其中pos[arr[i]].y即为i 其中pos[arr[i]].y即为i),所产生的最大值。

状态方程:详情看代码。

思路:先合并小区间,再合并大区间。因为大区间里可能会包含小区间,先合并小区间时有助于我们在合并大区间时进行决策——到底是合并掉这个区间直接取合并后的值大( d p [ l e f t ] + t m p [ l e f t − 1 ] dp[left] + tmp[left - 1] dp[left]+tmp[left1]),还是把这个区间看作纯填坑的数组不合并所拿的值大。对于两个区间交错的问题,我们先分别把这两个区间合并的值搞出来放那(放 d p [ i ] dp[i] dp[i]里),再在合并"包含这两个小区间"的大区间时进行决策取不取。

bool cmp(PII &a,PII &b){
    return a.y - a.x < b.y - b.x;      //长度比较
}

void solve() {
    //code here
    int n;cin >> n;
    int nn = 2 * n;
    vector<int>arr(nn + 2);         //存原始数据数组
    vector<PII>pos(n + 1);          //1-n数字对的左位置和右位置


    for(int i = 1;i <= nn;++i){        //初始数据处理
       cin >> arr[i];
       if(pos[arr[i]].x)pos[arr[i]].y = i;
       else pos[arr[i]].x = i;
    }

    vector<PII>up(n + 1);           //用于复制pos数组
    pos.push_back({0,nn+1});     //为了方便,最后遍历整个数组时而加入。与for(0~nn+1)没有本质区别

    up = pos;
    sort(up.begin(),up.end(),cmp);
    //用于复制pos数组,并按照区间长度从小到大排序
    //目的是为了让小区间先进行合并,大区间后合并,方便大区间包含“整个”小区间时,直接取max决策这个小区间是合并了贡献大还是不合并贡献大
    //如不懂可以先放着看下面

    vector<int>tmp(nn + 2);         //用于每个区间遍历时存值
    vector<int>dp(nn + 2);          //存每个区间遍历完后的值 dp[y] 为 区间[x,y]整合后的最大值(注意:这个值仅存于dp[y]
                                        //目的是方便下面那行max决策时取值

    for(auto [l,r] : up){       //相当于int l = up[i].x ,int r = up[i].y;
        tmp[l] = arr[l];                          //tmp[i] 遍历这个区间时暂时存合并这个最优值(合并好(如果能合并更小的区间)还是不合并好)
        for(int i = l + 1;i <= r;++i){
            tmp[i] = tmp[i - 1] + arr[l];         //不考虑其他,只是遍历

            int left = pos[arr[i]].x;             //假设这个位置存的值是右值   去存当前位置的这个值的左值

            if(left != i && left > l){            //如果当前确实是左值 且在这个大区间里  我就取max进行决策(到底是”合并这个区间值大“还是”不合并大“)
                tmp[i] = max(tmp[i],tmp[left - 1] + dp[i]);  //重点!  dp[i]就是合并这个小区间所获得的值(被合并的值都在dp[i]里),因为被取,所以左边剩下的tmp[left-1]
            }
        }
        dp[r] = tmp[r] ;                          //遍历完后 将合并这个区间的最优解放在dp[r]里
    }

    cout << dp[nn + 1] << endl;                   //输出整个区间的最优解
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值