题解 DTOJ #1098. 国王联盟 kingdoms

欢迎访问 My Luogu Space

【题目大意】

一个 n ∗ m n*m nm 的表格,每个格子里面都有一个不超过 k k k 的正整数。现在请你找出一个面积最大的矩形,使得矩形内部的不同的数字个数不超过 r r r,且出现在矩形内部的数字不能再出现在矩形外部。


【题解】

不难发现,由于我们需要求出的矩形内的数字不会再出现在矩形外部,因此矩形的左上角一定是能够完全包含这个数字的最小的矩形(不考虑出现别的数字)的左上角,其他三个角也同理。

我们先预处理出能够完整包含每个数字的最小矩形。

由于两个相对顶点确定一个矩形,我们可以 O ( n 2 ) O(n^2) O(n2) 枚举两个最小矩形作为答案矩形的两个顶点得到答案矩形,然后再 O ( n ) O(n) O(n) 枚举所有的最小矩形,判断每个最小矩形:

  1. 被包含在答案矩形内部
  2. 在答案矩形的外部
  3. 与答案矩形相交

如果在答案矩形内部,那么计数器加一;

如果与答案矩形相交,说明出现在矩形内部的数字也出现在了矩形的外部,与题目要求不符合,直接跳过当前的答案矩形。

如果没有跳过并且枚举完所有矩形之后,再判断计数器与 r r r 的大小关系。

最后更新答案。

效率 O ( n 3 ) O(n^3) O(n3)

加点随机打乱和剪枝可以挤到排名很前面。


【代码】

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000+10;
struct DATA{int u, d, l, r;}s[MAXN];

int n, m, k, r, ans, ct;
int f[MAXN], id[MAXN];

int main(){
	scanf("%d%d%d%d", &n, &m, &k, &r);
	for(int i=1; i<=k; ++i){
		s[i].u = 1e9;
		s[i].l = 1e9;
	}
	for(int i=1; i<=n; ++i)
		for(int j=1; j<=m; ++j){
			int a; scanf("%d", &a);
			if(!f[a]) id[a] = ++ct, f[a] = 1;
			s[id[a]].u = min(s[id[a]].u, i);
			s[id[a]].d = max(s[id[a]].d, i);
			s[id[a]].l = min(s[id[a]].l, j);
			s[id[a]].r = max(s[id[a]].r, j);
		}
	for(int i=1; i<=ct; ++i)
		for(int j=i; j<=ct; ++j){
			int U = min(s[i].u, s[j].u);
			int D = max(s[i].d, s[j].d);
			int L = min(s[i].l, s[j].l);
			int R = max(s[i].r, s[j].r);
			if((R-L+1)*(D-U+1) <= ans) continue;
			int can=1, cnt=0;
			for(int x=1; x<=ct; ++x){
				if(s[x].u>=U && s[x].d<=D && s[x].l>=L && s[x].r<=R) ++cnt;
				else if(s[x].d<U || s[x].u>D || s[x].l>R || s[x].r<L) continue;
				else can = 0;
				if(!can || cnt>r) break;
			}
			if(can && cnt<=r) ans = max(ans, (R-L+1)*(D-U+1));
		}
	printf("%d", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值