HDU6638 Snowy Smile - 稀疏矩陣的最大子矩陣和 - 座標離散化 - 線段樹 - 最大子段和

Snowy Smile

Time Limit: 4000/4000 MS (Java/Others)   Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 525   Accepted Submission(s): 165

Problem Description

There are n pirate chests buried in Byteland, labeled by 1,2,…,n. The i-th chest’s location is (xi,yi), and its value is wi, wi can be negative since the pirate can add some poisonous gases into the chest. When you open the i-th pirate chest, you will get wi value.

You want to make money from these pirate chests. You can select a rectangle, the sides of which are all paralleled to the axes, and then all the chests inside it or on its border will be opened. Note that you must open all the chests within that range regardless of their values are positive or negative. But you can choose a rectangle with nothing in it to get a zero sum.

Please write a program to find the best rectangle with maximum total value.

Input

The first line of the input contains an integer T(1≤T≤100), denoting the number of test cases.

In each test case, there is one integer n(1≤n≤2000) in the first line, denoting the number of pirate chests.

For the next n lines, each line contains three integers xi,yi,wi(−109≤xi,yi,wi≤109), denoting each pirate chest.

It is guaranteed that ∑n≤10000.

Output

For each test case, print a single line containing an integer, denoting the maximum total value.

Sample Input

2
4
1 1 50
2 1 50
1 2 50
2 2 -500
2
-1 1 5
-1 1 1

Sample Output

100
6

 
 

題目大概意思:

給出平面上 n(1n2×103)n(1≤n≤2×10^3) 個點,第 ii 個點的座標是 (xi,yi)(x_i,y_i) ,權值爲 wiw_i109xi,yi,wi109-10^9≤x_i,y_i,w_i≤10^9 ,用一個任意大小的邊與座標軸平行的矩形覆蓋點,求矩形能覆蓋的點的權值之和的最大值。

 
 

分析:

首先對座標進行離散化處理,則問題轉化爲了求一個 r×c (r,cn)r×c\,(r,c≤n) 的矩陣 MM 的所有子矩陣的權值之和的最大值。而由於只有 nn 個點,故 MM 中至多有 nn 個非零元。

對於一般的矩陣求最大子矩陣和,時間複雜度是 O(n3)O(n^3)O(n3log2n)O(n^3·\log_2n)

對於一維的最大子段和,我們可以使用尺取法在 O(n)O(n) 的時間複雜度下計算出來,也可以通過使用線段樹維護區間最大子段和,在 O(nlog2n)O(n·\log_2n) 的時間複雜度下計算出來。

而對於二維的問題,我們可以枚舉出矩陣的上下邊界,再計算每一列處於上下邊界之間的元素的和,將其轉化爲 n2n^2 個一維問題來解決。
 

O(n3)O(n^3) 的時間複雜度無法在時間限制內解決問題。因此我們考慮如何利用 MM 是稀疏矩陣這一特性降低時間複雜度。

考慮到矩陣中至多有 O(n)O(n) 個非零元,因此每一行只有 O(1)O(1) 個均攤的非零元,因此如果我們已經計算出了以 r1,r2r_1,r_2 爲上下邊界的子矩陣 M1M_1 的每一列的元素之和所組成的向量 v1\vec{v_1},那麼對於以 r1,r2+1r_1,r_2+1 爲上下邊界的子矩陣 M2M_2 的每一列的元素之和所組成的向量 v2\vec{v_2}v1\vec{v_1}v2\vec{v_2} 之間在均攤下只有 O(1)O(1) 個元素是不同的。因此如果我們使用了線段樹維護 v1\vec{v_1} 的最大子段和,則可以通過修改 O(1)O(1) 個元素將其轉爲維護 v2\vec{v_2} 的最大子段和的線段樹,而線段樹的每次更新的時間複雜度爲 O(log2n)O(\log_2n) ,因此我們藉助維護前一個上下邊界內元素的最大子段和的線段樹,可以在 O(log2n)O(\log_2n) 的時間複雜度內轉變爲維護當前上下邊界內元素的最大子段和的線段樹。那麼共有 O(n2)O(n^2) 種邊界,在每兩種曼哈頓距離11 的邊界之間轉移的時間複雜度是 O(log2n)O(\log_2n) ,故算法的總時間複雜度是 O(n2log2n)O(n^2·\log_2n) .

 
 
下面貼代碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int MAX_N = 2005;

struct Star
{
	int x;
	int y;
	int w;
};
Star p[MAX_N];
int kx[MAX_N];
int ky[MAX_N];

ll A[MAX_N][MAX_N];
int G[MAX_N][MAX_N];
int deg[MAX_N];


struct P
{
	ll left;
	ll right;
	ll all;
	ll rms;
	P() :left(0), right(0), all(0), rms(0) {}
	P(const ll& left, const ll& right, const ll& all, const ll& rms)
		:left(left), right(right), all(all), rms(rms) {}
};

P dat[(1 << 13) + 5];
int _n;
ll _max(const ll& a, const ll& b);
P unite(P ls, P rs);
void _init(const int n);
void update(const ll& x, int p);
P query(const int& left, const int& right, const int k, const int begin, const int end);


int main()
{
	int T, n;
	scanf("%d", &T);

	while (T--)
	{
		scanf("%d", &n);
		int kxcnt = 0;
		int kycnt = 0;

		for (int i = 0; i <= n; i++)
		{
			memset(A[i], 0, (n + 1) * sizeof(A[0][0]));
		}
		memset(deg, 0, sizeof(deg));

		for (int i = 0; i < n; i++)
		{
			Star& cur = p[i];
			scanf("%d%d%d", &cur.x, &cur.y, &cur.w);

			kx[kxcnt++] = cur.x;
			ky[kycnt++] = cur.y;
		}
		sort(kx, kx + kxcnt);
		sort(ky, ky + kycnt);
		kxcnt = unique(kx, kx + kxcnt) - kx;
		kycnt = unique(ky, ky + kycnt) - ky;
		for (int i = 0; i < n; i++)
		{
			Star& cur = p[i];
			cur.x = lower_bound(kx, kx + kxcnt, cur.x) - kx;
			cur.y = lower_bound(ky, ky + kycnt, cur.y) - ky;
			A[cur.x][cur.y] += cur.w;
		}
		for (int i = 0; i < kxcnt; i++)
		{
			for (int j = 0; j < kycnt; j++)
			{
				if (A[i][j])
				{
					G[i][deg[i]++] = j;
				}
			}
		}
		ll ans = 0;
		for (int i = 0; i < kxcnt; i++)
		{
			_init(kycnt);
			for (int j = i; j < kxcnt; j++)
			{
				for (int k = 0; k < deg[j]; k++)
				{
					update(A[j][G[j][k]], G[j][k]);
				}
				ans = _max(query(0, n - 1, 0, 0, _n).rms, ans);
			}
		}
		printf("%lld\n", ans);
	}
	return 0;
}

ll _max(const ll& a, const ll& b)
{
	return a > b ? a : b;
}

P unite(P ls, P rs)
{
	P res;
	res.all = ls.all + rs.all;
	res.left = _max(ls.left, ls.all + rs.left);
	res.right = _max(rs.right, rs.all + ls.right);
	res.rms = _max(_max(ls.rms, rs.rms), ls.right + rs.left);
	return res;
}

void _init(const int n)
{
	_n = 1;
	while (_n < n)
	{
		_n <<= 1;
	}
	--_n;
	memset(dat, 0, 2 * (_n + 1) * sizeof(dat[0]));
}

void update(const ll& x, int p)
{
	p += _n;
	P& cur = dat[p];
	cur.all += x;
	cur.left = cur.right = cur.rms = _max(0ll, cur.all);
	while (p)
	{
		p = (p - 1) >> 1;
		dat[p] = unite(dat[(p << 1) + 1], dat[(p << 1) + 2]);
	}
}

P query(const int& left, const int& right, const int k, const int begin, const int end)
{
	if (left <= begin && end <= right)
	{
		return dat[k];
	}
	else if (left <= end && begin <= right)
	{
		return unite
		(
			query(left, right, (k << 1) + 1, begin, (begin + end) >> 1),
			query(left, right, (k << 1) + 2, ((begin + end) >> 1) + 1, end)
		);
	}
	return P();
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章