@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@
二分之前先判是否有合法解会更快一点。
来源:https://www.cnblogs.com/Tiw-Air-OAO/p/12419713.html