题目
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)
*/