【HDU6638 Snowy Smile】最大子矩阵线段树维护

传送门

题意

给一个非常稀疏的矩阵,求它的最大子矩阵。

思路

首先将纵坐标离散化,然后枚举横坐标的上下限。
将所有点按照横坐标排序,枚举矩形的上边界,建空树,用线段树维护纵向的最大子段和。
对于每个上边界,逐行加入点,每加入一行就相当于是有了下边界,此时线段树维护的最大子段和即为可能的答案。
所有可能的答案还有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);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值