zoukankan      html  css  js  c++  java
  • @loj


    @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} floor) 小的数)能够达到最小就更好了。

    你能帮帮明明吗?

    原题传送门。

    @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
    ", ans.x, ans.y);
    }
    
    int main() {
    	srand(20041112);
    	int T; scanf("%d", &T);
    	while( T-- ) solve();
    }
    

    @details@

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

  • 相关阅读:
    5.装饰器生成器迭代器
    python-进程&线程
    模块(二)
    MYSQL数据库操作(一)
    python-网络编程-socket编程
    python-异常处理
    python-面向对象进阶
    python-模块
    python_面向对象编程
    生成器 迭代器,装饰器 ,软件开发规范
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12419713.html
Copyright © 2011-2022 走看看