题意
给一个非常稀疏的矩阵,求它的最大子矩阵。
思路
首先将纵坐标离散化,然后枚举横坐标的上下限。
将所有点按照横坐标排序,枚举矩形的上边界,建空树,用线段树维护纵向的最大子段和。
对于每个上边界,逐行加入点,每加入一行就相当于是有了下边界,此时线段树维护的最大子段和即为可能的答案。
所有可能的答案还有0求最大值,就是答案啦。
题解
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2010,M=4100;
int Case,n,m,i,j,k,cb,b[N],pos[N];ll pre[M],suf[M],s[M],v[M],ans;
struct E{
int x,y,z;
}e[N];
inline bool cmp(const E&a,const E&b){
return a.x < b.x;
}
inline ll ls(ll x){return x<<1;}
inline ll rs(ll x){return x<<1|1;}
void build(int x,int a,int b){
pre[x] = suf[x] = s[x] = v[x] = 0;
if(a == b){
pos[a] = x;//对每个坐标,给出其在线段树中叶子的位置
return;
}
int mid = (a+b)>>1;
build(ls(x),a,mid);
build(rs(x),mid+1,b);
}
inline void change(int x,int p){
x = pos[x]; //还原为线段树中的位置
s[x] += p; //单点修改
if(s[x] > 0)
pre[x] = suf[x] = v[x] = s[x];
else
pre[x] = suf[x] = v[x] = 0;
for(x >>= 1; x ; x >>= 1){ //z..zkw?!
pre[x] = max(pre[ls(x)], s[ls(x)]+pre[rs(x)]); //最长前缀
suf[x] = max(suf[rs(x)], s[rs(x)]+suf[ls(x)]); //最长后缀
s[x] = s[ls(x)] + s[rs(x)]; //总和
v[x] = max(max(v[ls(x)],v[rs(x)]), suf[ls(x)]+pre[rs(x)]); //最大子段和
}
}
int main(){
scanf("%d", &Case);
while(Case --){
scanf("%d",&n);
for(cb = 0, i = 1; i <= n; i ++){
scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].z);
b[++ cb] = e[i].y;//只对y坐标离散化
}
sort(b+1, b+cb+1);
m = unique(b+1,b+cb+1)-b-1;
//按x坐标排序
sort(e+1, e+n+1, cmp);
ans = 0;
//预处理:对y坐标离散化
for(i = 1; i <= n; i ++)
e[i].y = lower_bound(b+1,b+m+1,e[i].y)-b;
for(i = 1; i <= n; i ++)
if(i == 1 || e[i].x != e[i-1].x){ //每个x只作为上边界一次
build(1, 1, m);
//枚举下边界j
for(j = i; j <= n; j = k){
//现在有了矩形的上下边界,将每一行的点加进线段树
for(k = j; k <= n && e[j].x == e[k].x; k ++)//和下边界同一行。因为逐行枚举,只需等于即可
change(e[k].y, e[k].z);// 在y位置插入z
//线段树维护的是y方向的最大子段和,v[1]就是全树的最大子段和
if(ans < v[1]) ans = v[1];
}
}
printf("%lld\n",ans);
}
}