P7450 [THUSCH2017] 巧克力 题解

P7450 [THUSCH2017] 巧克力

题面:

题目描述

「人生就像一盒巧克力,你永远不知道吃到的下一块是什么味道。」

明明收到了一大块巧克力,里面有若干小块,排成 n n n m m m 列。每一小块都有自己特别的图案 ,它们有的是海星,有的是贝壳,有的是海螺……其中还有一些因为挤压,已经分辨不出是什么图案了。明明给每一小块巧克力标上了一个美味值 a i , j a_{i,j} ai,j 0 ≤ a i , j ≤ 1 0 6 0\le a_{i,j}\le 10^6 0ai,j106),这个值越大,表示这一小块巧克力越美味。

正当明明咽了咽口水,准备享用美味时,舟舟神奇地出现了。看到舟舟恳求的目光,明明决定从中选出一些小块与舟舟一同分享。

舟舟希望这些被选出的巧克力是连通的(两块巧克力连通当且仅当他们有公共边),而且这些巧克力要包含至少 k k k 1 ≤ k ≤ 5 1\le k\le 5 1k5)种。而那些被挤压过的巧克力则是不能被选中的。

明明想满足舟舟的愿望,但他又有点「抠」,想将美味尽可能多地留给自己。所以明明希望选出的巧克力块数能够尽可能地少。如果在选出的块数最少的前提下,美味值的中位数(我们定义 n n n 个数的中位数为第 ⌊ n + 1 2 ⌋ \left\lfloor\frac{n+1}{2}\right\rfloor 2n+1 小的数)能够达到最小就更好了。

你能帮帮明明吗?

输入格式

每个测试点包含多组测试数据。

输入第一行包含一个正整数 T T T 1 ≤ T ≤ 5 1\le T\le 5 1T5),表示测试数据组数。

对于每组测试数据:

输入第一行包含三个正整数 n , m n,m n,m k k k

接下来 n n n 行,每行 m m m 个整数,表示每小块的图案 c i , j c_{i,j} ci,j。若 c i , j = − 1 c_{i,j}=-1 ci,j=1 表示这一小块受到过挤压,不能被选中;

接下来 n n n 行,每行 m m m 个整数,表示每个小块的美味值 a i , j a_{i,j} ai,j

输出格式

输出共包括 T T T 行,每行包含两个整数,用空格隔开,即最少的块数和最小的美味值中位数。

若对于某组测试数据,不存在任意一种合法的选取方案,请在对应行输出两个 − 1 -1 1

样例 #1
样例输入 #1
1
5 4 5
3 4 3 4
5 5 -1 5
-1 4 5 5
5 5 4 2
1 -1 2 4
1 3 1 1
3 2 3 3
4 4 4 5
8 9 9 5
7 2 6 3
样例输出 #1
9 5
提示
测试点编号 n , m n,m n,m 的限制 c i , j c_{i,j} ci,j 的限制部分分说明
1 n = 1 , 1 ≤ m ≤ 233 n=1,1\le m\le233 n=1,1m233 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ n × m 1\le c_{i,j}\le n\times m 1ci,jn×m A \text{A} A
2 1 ≤ n × m ≤ 20 1\le n\times m\le 20 1n×m20 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ n × m 1\le c_{i,j}\le n\times m 1ci,jn×m A \text{A} A
3~4 n = 2 , m = 15 n=2,m=15 n=2,m=15 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ n × m 1\le c_{i,j}\le n\times m 1ci,jn×m A \text{A} A
5~6 1 ≤ n × m ≤ 30 1\le n\times m\le 30 1n×m30 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ n × m 1\le c_{i,j}\le n\times m 1ci,jn×m A \text{A} A
7~9 1 ≤ n × m ≤ 50 1\le n\times m\le 50 1n×m50 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ 8 1\le c_{i,j}\le8 1ci,j8 A \text{A} A
10 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1n×m233 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ 8 1\le c_{i,j}\le8 1ci,j8 A \text{A} A
11~12 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1n×m233 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ 8 1\le c_{i,j}\le8 1ci,j8 B \text{B} B
13~15 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1n×m233 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ 14 1\le c_{i,j}\le14 1ci,j14 B \text{B} B
16~20 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1n×m233 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ n × m 1\le c_{i,j}\le n\times m 1ci,jn×m B \text{B} B
21 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1n×m233 c i , j = − 1 c_{i,j}=-1 ci,j=1 1 ≤ c i , j ≤ n × m 1\le c_{i,j}\le n\times m 1ci,jn×m该测试点不计分。

A \text{A} A:若输出的最少块数均正确,但最小中位数存在错误,选手可以获得该测试点 80 % 80\% 80% 的分数。
B \text{B} B:若输出的最少块数均正确,但最小中位数存在错误,选手可以获得该测试点 60 % 60\% 60% 的分数。

很好很好的 color-coding \text{color-coding} color-coding,让我的随机化旋转!

我们很难去描述 k k k 中不同的颜色,但我们发现 k ∈ [ 1 ∼ 5 ] k \in [1\sim 5] k[15]

这意味着我们能乱搞(随机化大法)!

我们将每种颜色随机映射到 [ 0 , k ) [0,k) [0,k) 中,我们就自欺欺人的在上面跑最小斯坦纳树。

当最优解包含的 k k k 个点被分配到不同的颜色中即可正确,一次成功的概率大概是 P = k ! / k P=k!/k P=k!/k

这个的出错概率很高,怎么办呢? ——答案是 多随几次

考虑随机 200 200 200 次,正确性就已经可以接受了。

我们用斯坦纳树先求出最小的块数,如果最小的块数都无法满足要求,直接输出 0 0

最小化中位数:这个也不好办,不好办的时候,考虑二分总是不错的选择!

我们二分中位数,如果大于二分值,记为 1 1 1,否则记为 − 1 -1 1

我们在去跑多次随机化最小斯坦纳树,与 0 0 0 比较,如果大,说明这个二分值偏小,否则说明二分值偏大。

最后就可以得到最优解!

AC-code:

#include<bits/stdc++.h>
using namespace std;
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}
void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
int dx[4] = {1,-1,0,0};
int dy[4] = {0,0,1,-1};
const int inf = 0x3f3f3f3f,N = 300;
mt19937 rnd(20080623);
int c[N][N],a[N][N],f[N][N][1<<6],w[N][N],col[N],to[N],vis[N][N];

void solve() {
	int n = rd(),m = rd(),k = rd(),top = 0;
	for(int i = 1;i<=n;i++)
		for(int j = 1;j<=m;j++)
			col[++top] = c[i][j] = rd();
	sort(col + 1,col + top + 1);
	top = unique(col + 1,col + top + 1) - col - 1;
	for(int i = 1;i<=n;i++)
		for(int j = 1;j<=m;j++)
			a[i][j] = rd(),w[i][j] = 1;
	auto check = [&](int x,int y) -> bool {
		return (x >= 1 && x <= n && y >= 1 && y <= m);
	};
	auto spfa = [&](int S) -> void {
		queue<array<int,2>> q;
		for(int i = 1;i<=n;i++)
			for(int j = 1;j<=m;j++)
				if(f[i][j][S] ^ inf) 
					q.emplace(array<int,2>{i,j});
		while(!q.empty()) {
			array<int,2> p = q.front();
			q.pop();
			vis[p[0]][p[1]] = false;
			for(int i = 0;i<4;i++) {
				int tx = p[0] + dx[i];
				int ty = p[1] + dy[i];
				int W = w[tx][ty];
				if(c[tx][ty] == -1 || !check(tx,ty)) continue;
				if(f[tx][ty][S] > f[p[0]][p[1]][S] + W) {
					f[tx][ty][S] = f[p[0]][p[1]][S] + W;
					if(vis[tx][ty]) continue;
					q.emplace(array<int,2>{tx,ty});
					vis[tx][ty] = true;
				}
			}
		}
	};
	auto work = [&]() -> int{
		int ans = inf;
		for(int P = 1;P <= 233;P++) {
			shuffle(col + 1,col + top + 1,rnd);
			for(int i = 1;i<=top;i++)
				to[col[i]] = i % k;
			for(int i = 1;i<=n;i++)
				for(int j = 1;j<=m;j++){
					for(int s = 0;s < (1 << k);s++) f[i][j][s] = inf;
					if(~c[i][j]) f[i][j][1 << to[c[i][j]]] = w[i][j];
				}
			for(int S = 1;S < (1<<k);S++) {
				for(int i = 1;i<=n;i++)
					for(int j = 1;j<=m;j++)
						if(~c[i][j])
							for(int T = S & (S - 1);T;T = (T - 1) & S)
								if(T >= (S ^ T))
									f[i][j][S] = min(f[i][j][T] + f[i][j][S ^ T] - w[i][j],f[i][j][S]);
				spfa(S);
			}
			for(int i = 1;i<=n;i++)
				for(int j = 1;j<=m;j++)
					ans = min(ans,f[i][j][(1<<k) - 1]);
		}
		return ans;
	};
	int re = work();
	if(re == inf) {puts("-1 -1");return;}
	int l = 0,r = 1e6;
	while(l < r) {
		int mid = (l + r) >> 1;
		for(int i = 1;i<=n;i++)
			for(int j = 1;j<=m;j++)
					w[i][j] = ((a[i][j] <= mid) ? 9999 : 10001);
		int ce = work();
		if(ce > re * 10000) l = mid + 1;
		else r = mid;
	}
	wt(re);putchar(' ');wt(l);putchar('\n');
}
signed main() {
	int T = rd();
	while(T--) solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值