P1502 窗口的星星(线段树+扫描线)

题目描述

小卡买到了一套新房子,他十分的高兴,在房间里转来转去。
晚上,小卡从阳台望出去,“哇~~~~好多星星啊”,但他还没给其他房间设一个窗户。
天真的小卡总是希望能够在晚上能看到最多最亮的星星,但是窗子的大小是固定的,边也必须和地面平行。
这时小卡使用了超能力(透视术)知道了墙后面每个星星的位置和亮度,但是小卡发动超能力后就很疲劳,只好拜托你告诉他最多能够有总和多亮的星星能出现在窗口上。
小卡买的窗户框是金属做的,所以在边框上的不算在内。

输入格式

本题有多组数据,第一行为 T,表示有 T 组数据。
对于每组数据:
第一行 3 个整数 n,W,H,W, 表示有 n 颗星星,窗口宽为 W,高为 H。
接下来 n 行,每行三个整数 x i , y i , l i x_i,y_i,l_i xi,yi,li 表示星星的坐标在 ( x i , y i ) (x_i,y_i) (xi,yi)上,亮度为 l i l_i li

样例:

2
3 5 4
1 2 3
2 3 2
6 3 1
3 5 4
1 2 3
2 3 2
5 3 1

输出格式

T 个整数,表示每组数据中窗口星星亮度总和的最大值。

样例:

5
6

算法简述

对于每一个星星,包含这个星星的最优矩形有四种情况,即这个星星在矩形的边角处,这里可以取星星在左下角的情况, ( x i , y i ) (x_i,y_i) (xi,yi)对应一个 ( x i + W − 1 , y i + H − 1 ) (x_i+W-1,y_i+H-1) (xi+W1,yi+H1)的矩阵,最优解一定在这n个矩阵其中之一。
用线段树+扫描线处理这n个矩阵。
这里用对x轴用线段树,从下到上处理每条线。

线段树

我们将矩阵简化为区间,线段树维护区间的最大值

扫描线

对于每个矩阵,记录上下两条边

struct line
{
	int h, l, r, v;
	bool operator <(const line& x)const
	{
		if(h!=x.h) return h < x.h;
		return v > x.v;
	}
};
line seg[maxn << 1];

h是这条边的高度
l,r是左右端点
v是这个矩阵对应的星星的亮度
对所有边从低到高排序,依次处理
因为边是从低到高依次处理,并且用线段树维护区间的最大值,那么可以将矩阵的下边的价值设为星星的亮度,矩阵上边的价值设为下边的相反数,这样就可以完成求矩阵最大价值的目的
处理边 s e g [ i ] seg[i] seg[i],给这个边对应的区间加这个边的价值。
每次处理完之后,线段树的根节点就是当前最优解。

细节

如何维护区间最大值?

离散化,每个星星对应的区间 ( x , x + W − 1 ) (x,x+W-1) (x,x+W1),把所有区间端点记录一下,排序,去掉重复点就是线段树需要维护的区间范围。线段树的叶子节点是 ( x x i , x x i ) (xx_i,xx_i) xxi,xxi,对每个边进行更改时,先要定位到左右端点的i,再更改i区间的值
unique函数可以直接去重

为什么线段树根节点就是当前最优解?

当处理进星星的有效范围时(y),会有一条下边会给区间加星星的价值
当处理出星星的有效范围时(y),会有一条上边会给区间减星星的价值

总结

其实也是一种暴力,从下到上,很巧妙的遍历的所有可能性

代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#pragma GCC optimize(2)
#pragma warning(disable:4996)
using namespace std;
const int maxn = 10001;
struct line
{
	int h, l, r, v;
	bool operator <(const line& x)const
	{
		if(h!=x.h) return h < x.h;
		return v > x.v;
	}
};
line seg[maxn << 1];
int xx[maxn << 1];
int maxv[maxn << 3], tag[maxn << 3];
void update_lower(int x,int l,int r)
{
	if (tag[x] && l != r)
	{
		tag[x << 1] += tag[x];
		maxv[x << 1] += tag[x];
		tag[x << 1 | 1] += tag[x];
		maxv[x << 1 | 1] += tag[x];
		tag[x] = 0;
	}
}
void change(int x, int l, int r, int ql, int qr, int k)
{
	if (l >= ql && r <= qr)
	{
		maxv[x] += k;
		tag[x] += k;
		return;
	}
	update_lower(x, l, r);
	int mid = (l + r) >> 1;
	if (mid >= ql) change(x << 1, l, mid, ql, qr, k);
	if (mid < qr) change(x << 1 | 1, mid + 1, r, ql, qr, k);
	maxv[x] = max(maxv[x << 1], maxv[x << 1 | 1]);
}
int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		int n, w, h; scanf("%d%d%d", &n, &w, &h);	
		int x, y, l;
		memset(maxv, 0, sizeof(maxv));
		memset(tag, 0, sizeof(tag));
		for (int i = 1; i <= n; i++)
		{
			scanf("%d%d%d", &x,&y,&l);
			seg[i] = { y,x,x + w - 1,l };
			seg[i + n] = { y + h - 1,x,x + w - 1,-l };
			xx[i] = x;
			xx[i + n] = x + w - 1;
		}
		n = n << 1;
		sort(seg + 1, seg + 1 + n);
		sort(xx + 1, xx + 1 + n);
		int cntx = unique(xx + 1, xx + 1 + n) - xx - 1;
		int ans = 0;
		
		for (int i = 1; i <= n; i++)
		{
			int L = lower_bound(xx + 1, xx + 1 + cntx, seg[i].l) - xx;
			int R = lower_bound(xx + 1, xx + 1 + cntx, seg[i].r) - xx;
			change(1, 1, cntx, L, R, seg[i].v);
			ans = max(ans, maxv[1]);
		}
		cout << ans << '\n';
	}
	return 0;
}

你可能感兴趣的:(线段树,扫描线,算法,数据结构,acm竞赛)