Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final Round) D. Boss, Thirsty(前缀后缀max dp优化)

题目

n*m(n*m<=2e5)的矩阵,第i行第j列有一个权值aij,

第i行需要选一个连续的区间[li,ri],

并且需要满足第i行选的区间和第i-1行选的区间至少有一个公共点,

且第i行选的区间,需要至少有一个点是第i-1行选的区间没有的,

问满足条件下的选的最大权值和

思路来源

2024牛客多校第六场I题Intersecting Intervals

题解

这个题和这个牛客的做法很类似,

牛客的做法是把区间收拢到每一个出现的点上

而这个题是把区间收拢到两个端点上

注意到性质,

上一个区间选了[l,r],那当前区间要么必选[l-1,l],要么必选[r,r+1],那就可以转移了

f[i][j]表示i行j列作为左端点必取的最大和

g[i][j]表示i行j列作为右端点必取的最大和

有以下四种转移

f[i][j]=max(k从j+1到m f[i-1][k]+sum[j,k]+max(0,sufmax[k+1:m])) sum[k]-sum[j-1] (f1)

g[i][j]=max(k从1到j-1 g[i-1][k]+sum[k,j]+max(0,premax[1:k-1])) sum[j]-sum[k-1] (g1)

f[i][j]=max(k从j到m-1 g[i-1][k]+sum[j,k+1]+max(0,sufmax[k+2:m])) sum[k+1]-sum[j-1] (g2)

g[i][j]=max(k从2到j f[i-1][k]+sum[k-1,j]+max(0,premax[1:k-2])) sum[j]-sum[k-2] (f2)

针对第一种转移,不妨称为f1,解释一下,

比如i-1行选了[k,x],那么第i行左端点突出来(之前未选的)至少一个点j,

[j,k]相交,满足有一个交点k的约束,

那么k+1往后的可取可不取,

如果取的话是取k+1开始的一个最大后缀,所以求最大子段和与0取max

g1的转移是针对g镜像对称的

一开始以为只有这两种,但是会发现f的左端点越来越靠左,g的右端点越来越靠右

应该需要f从一个左侧的区间往右的转移

所以有了g2的转移,

考虑g2,就是i-1行选了区间[y,k],当前选[j,z],

因为并没有记录y是多少,所以钦定的至少一个之前未选的点k+1,

而[j,k]是一段公共的交点,这样就包含了y<=j的情况

g同理,有一个f2的转移

发现转移的话,把和k有关的项提出来之后,

后一次决策的点只会比前一次决策的点更多,所以可以叫决策单调性

当然你发现需要维护的这个式子对于每次决策是固定的,

并且每次只会新增一项,所以直接记录最优端点就完了

由于题目的这个约束很难统一,所以第一行单独处理

代码

#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<map>
#include<set>
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 pb push_back
#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 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=105,M=1e4+10;
const ll INF=1e15;
int t,n,m;
ll sol(){
    sci(n),sci(m);
    vector<vector<int>>a(n+1,vector<int>(m+1,0));
    vector<vector<ll>>f(n+1,vector<ll>(m+2,-INF));
    vector<vector<ll>>g(n+1,vector<ll>(m+2,-INF));
    vector<ll>pre(m+2,-INF),suf(m+2,-INF),sum(m+2,0);
    ll ans=-INF;
    rep(i,1,n){
        pre[0]=suf[m+1]=-INF;
        rep(j,1,m){
            sci(a[i][j]);
            sum[j]=sum[j-1]+a[i][j];
        }
        rep(j,1,m){
            pre[j]=max(pre[j-1],sum[m]-sum[j-1]);
        }
        per(j,m,1){
            suf[j]=max(suf[j+1],sum[j]);
        }
        auto premax=[&](int j){
            if(j<1)return 0ll;
            return pre[j]-(sum[m]-sum[j]);
        };
        auto sufmax=[&](int j){
            if(j>m)return 0ll;
            return suf[j]-sum[j-1];
        };
        if(i==1){
            rep(j,1,m){
                f[i][j]=sufmax(j);
                g[i][j]=premax(j);
            }
        }
        else{
            auto f1=[&](int k){
                return f[i-1][k]+sum[k]+max(0ll,sufmax(k+1));
            };
            auto f2=[&](int k){
                return f[i-1][k]-sum[k-2]+max(0ll,premax(k-2));
            };
            auto g1=[&](int k){
                return g[i-1][k]-sum[k-1]+max(0ll,premax(k-1));
            };
            auto g2=[&](int k){
                return g[i-1][k]+sum[k+1]+max(0ll,sufmax(k+2));
            };
            int p1=m,p2=m-1;
            per(j,m-1,1){
                if(f1(j+1)>f1(p1))p1=j+1;
                if(g2(j)>g2(p2))p2=j;
                f[i][j]=max(f1(p1),g2(p2))-sum[j-1];
            }
            int p3=1,p4=2;
            rep(j,2,m){
                if(g1(j-1)>g1(p3))p3=j-1;
                if(f2(j)>f2(p4))p4=j;
                g[i][j]=max(g1(p3),f2(p4))+sum[j];
            }
        }
        // rep(j,1,m){
        //     printf("i:%d j:%d f:%lld g:%lld\n",i,j,f[i][j],g[i][j]);
        // }
    }
    rep(j,1,m){
        ans=max(ans,f[n][j]);
        ans=max(ans,g[n][j]);
    }
    return ans;
}
int main(){
    sci(t);
    while(t--){
        ptlle(sol());
    }
    return 0;
}
/*
f[i][j]表示i行j列作为左端点必取的最大和
g[i][j]表示i行j列作为右端点必取的最大和
f[i][j]=max(k从j+1到m f[i-1][k]+sum[j,k]+sufmax[k+1:m]) sum[k]-sum[j-1] (f1)
g[i][j]=max(k从1到j-1 g[i-1][k]+sum[k,j]+premax[1:k-1]) sum[j]-sum[k-1] (g1)
f[i][j]=max(k从j到m-1 g[i-1][k]+sum[j,k+1]+sufmax[k+2:m]) sum[k+1]-sum[j-1] (g2)
g[i][j]=max(k从2到j f[i-1][k]+sum[k-1,j]+premax[1:k-2]) sum[j]-sum[k-2] (f2)
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Code92007

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

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

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

打赏作者

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

抵扣说明:

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

余额充值