@loj - 2977@ 「THUSCH 2017」巧克力


@description@

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

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

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

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

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

你能帮帮明明吗?

原题传送门。

@solution@

当 c 比较小时,求块数最少的包含 k 种颜色的连通块,其实就是一个斯坦纳树问题。

可以用二分的方法求中位数 x:将 <= x 记作 -1,> x 记作 1,则中位数是满足权值和 <= 0 的最小 x。因此做斯坦纳树的时候再加一维即可。

然而这个算法只适用于 c 较小的情况。但是注意到 k 依然非常小,也许我们依然可以沿用二分 + 斯坦纳树这一思路。

我们把所有颜色随机分为 k 类,再对这 k 个类搞斯坦纳树;可以发现这样的随机算法做一次成功的概率是 \(\frac{k!}{k^k}\)
因为 k 非常小,做个 150 次是没有问题的,失败概率大概 0.003(当然你常数小的话可以多做几次)。

@accepted code@

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

const int MAXN = 233;
const int MAX = int(1E6);
const int dx[] = {0, 0, 1, -1};
const int dy[] = {1, -1, 0, 0};

struct node{
	int x, y; node() {}
	node(int _x, int _y) : x(_x), y(_y) {}
	friend node operator + (node a, node b) {
		return node(a.x + b.x, a.y + b.y);
	}
	friend bool operator < (node a, node b) {
		return (a.x == b.x ? a.y < b.y : a.x < b.x);
	}
}f[MAXN + 5][MAXN + 5][1 << 5];

int n, m, k, t;
int b[MAXN + 5][MAXN + 5], d[MAXN + 5][MAXN + 5];

bool inq[MAXN + 5][MAXN + 5];
void run(int s) {
	queue<node>que;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if( b[i][j] != -1 ) que.push(node(i, j)), inq[i][j] = true;
	while( !que.empty() ) {
		node p = que.front(); que.pop(); inq[p.x][p.y] = false;
		for(int i=0;i<4;i++) {
			int x0 = p.x + dx[i], y0 = p.y + dy[i];
			if( x0 < 1 || y0 < 1 || x0 > n || y0 > m || b[x0][y0] == -1 ) continue;
			if( f[p.x][p.y][s] + node(1, d[p.x][p.y]) < f[x0][y0][s] ) {
				f[x0][y0][s] = f[p.x][p.y][s] + node(1, d[p.x][p.y]);
				if( !inq[x0][y0] ) que.push(node(x0, y0)), inq[x0][y0] = true;
			}
		}
	}
}
node check() {
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			if( b[i][j] == -1 ) continue;
			for(int s=0;s<t;s++)
				f[i][j][s] = node(MAX, 0);
			f[i][j][1 << b[i][j]] = node(0, 0);
		}
	for(int s=1;s<t;s++) {
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++) {
				if( b[i][j] == -1 ) continue;
				int s2 = s & (s - 1);
				while( s2 ) {
					f[i][j][s] = min(f[i][j][s], f[i][j][s2] + f[i][j][s^s2]);
					s2 = s & (s2 - 1);
				}
			}
		run(s);
	}
	node p = node(MAX, MAX);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			if( b[i][j] == -1 ) continue;
			p = min(p, f[i][j][t - 1] + node(1, d[i][j]));
		}
	return p;
}
int a[MAXN + 5][MAXN + 5];
node get() {
	if( check().x == MAX ) return node(MAX, MAX);
	int l = 0, r = MAX;
	while( l < r ) {
		int mid = (l + r) >> 1;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				d[i][j] = (a[i][j] <= mid ? -1 : 1);
		if( check().y <= 0 ) r = mid;
		else l = mid + 1;
	}
	return node(check().x, l);
}

node ans;
void update(node p) {
	if( ans.x == -1 ) {
		if( p.x != MAX ) ans = p;
	}
	else ans = min(ans, p);
}
int c[MAXN + 5][MAXN + 5], num[MAXN + 5];
void solve() {
	int p; scanf("%d%d%d", &n, &m, &k), p = n*m, t = (1 << k);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d", &c[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d", &a[i][j]);
	ans.x = ans.y = -1;
	for(int i=1;i<=150;i++) {
		for(int j=1;j<=p;j++) num[j] = rand() % k;
		for(int x=1;x<=n;x++)
			for(int y=1;y<=m;y++)
				b[x][y] = (c[x][y] == -1 ? -1 : num[c[x][y]]);
		update(get());
	}
	printf("%d %d\n", ans.x, ans.y);
}

int main() {
	srand(20041112);
	int T; scanf("%d", &T);
	while( T-- ) solve();
}

@details@

二分之前先判是否有合法解会更快一点。

posted @ 2020-03-05 13:21  Tiw_Air_OAO  阅读(228)  评论(0编辑  收藏  举报