[Luogu P3474] [BZOJ 1127] [POI2008]KUP-Plot purchase

洛谷传送门
BZOJ传送门

题目描述

给一个 n ∗ n n*n nn的地图,每个格子有一个价格,找一个矩形区域,使其价格总和位于 [ k , 2 k ] [k,2k] [k,2k]

输入输出格式

输入格式

输入 k   n   ( n &lt; 2000 ) k\ n\ (n&lt;2000) k n n<2000)和一个 n ∗ n n*n nn的地图

输出格式

输出矩形的左上和右下的列-行坐标或 N I E NIE NIE

输入输出样例

输入样例#1:
4 3
1 1 1
1 9 1
1 1 1
输出样例#1:
NIE
输入样例#2:
8 4
1 2 1 3
25 1 2 1
4 20 3 3
3 30 12 2
输出样例#2:
2 1 4 2

数据范围

1 ≤ k ≤ 1 0 9 1\le k\le 10^9 1k109 每个价格都是不大于 2 ∗ 1 0 9 2*10^9 2109的非负整数

解题分析

首先, 如果有满足条件的格子, 直接输出即可。

如果某个格子的权值 &gt; 2 k &gt;2k >2k, 这个格子一定就不在任何一个满足要求的矩形内。

现在我们就只能用权值 &lt; k &lt;k <k的格子了。 注意到可取的范围是 [ k , 2 k ] [k,2k] [k,2k], 这意味着如果有一个矩形权值和 &gt; 2 k &gt;2k >2k, 其内部一定有个子矩形满足条件。

那么我们用悬线法得到所有以某个点为右下角的极大子矩形, 然后得到权值和最大的。 如果这个权值和仍 &lt; k &lt;k <k, 显然无解。

否则我们一行一行地删, 如果某行权值 ∈ [ k , 2 k ] \in[k,2k] [k,2k], 那么直接输出这一行。 如果某行权值 &gt; 2 k &gt;2k >2k, 直接在这一行中一个一个地删就好了。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#define R register
#define IN inline
#define MX 2050
#define W while
#define gc getchar()
#define ll long long
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc);
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
}
ll k, mx;
int n, top;
int val[MX][MX], up[MX][MX];
bool out[MX][MX];
int le, ri, upp, dow;
ll sum[MX][MX];
struct INFO {int pos, h;}sta[MX];
IN ll $(R int l, R int r, R int u, R int d)
{return sum[d][r] - sum[d][l - 1] - sum[u - 1][r] + sum[u - 1][l - 1];}
IN void $$(R int l, R int r, R int u, R int d)
{
	ll buf = sum[d][r] - sum[d][l - 1] - sum[u - 1][r] + sum[u - 1][l - 1];
	if (buf > mx) le = l, ri = r, upp = u, dow = d, mx = buf;
}
int main(void)
{
	in(k), in(n); ll buf;
	for (R int i = 1; i <= n; ++i)
	for (R int j = 1; j <= n; ++j)
	{
		in(val[i][j]);
		if (val[i][j] >= k && val[i][j] <= k * 2)
		return printf("%d %d %d %d", j, i, j, i), 0;
		if (val[i][j] > k * 2) out[i][j] = true;
	}
	for (R int i = 1; i <= n; ++i)
	for (R int j = 1; j <= n; ++j)
	sum[i][j] = sum[i][j - 1] + (out[i][j]? 0 : val[i][j]);
	for (R int j = 1; j <= n; ++j)
	for (R int i = 1; i <= n; ++i)
	sum[i][j] += sum[i - 1][j];
	for (R int i = 1; i <= n; ++i)
	for (R int j = 1; j <= n; ++j)
	{
		if (out[i][j]) up[i][j] = 0;
		else up[i][j] = up[i - 1][j] + 1;
	}
	for (R int i = 1; i <= n; ++i)
	{
		sta[top = 1] = {0, 0};
		for (R int j = 1; j <= n; ++j)
		{
			W (top && sta[top].h >= up[i][j])
			{
				$$(sta[top - 1].pos + 1, j - 1, i - sta[top].h + 1, i);
				top--;
			}
			sta[++top] = {j, up[i][j]};
		}
		W (top > 1)
		{
			$$(sta[top - 1].pos + 1, n, i - sta[top].h + 1, i);
			top--;
		}
	}
	if (mx < k) return puts("NIE"), 0;
	if (mx <= 2 * k) return printf("%d %d %d %d", le, upp, ri, dow), 0;
	for (R int i = upp; i <= dow; ++i)
	{
		buf = $(le, ri, i, i);
		if (buf >= k && buf <= 2 * k) return printf("%d %d %d %d", le, i, ri, i), 0;
		else if (buf > 2 * k)
		{
			for (R int j = le; j <= ri; ++j)
			{
				if (out[i][j]) continue;
				buf -= val[i][j];
				if (buf <= 2 * k) return printf("%d %d %d %d", j + 1, i, ri, i), 0;
			}
		}
		mx -= buf;
		if (mx <= 2 * k) return printf("%d %d %d %d", le, i + 1, ri, dow), 0;
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值