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

北战南征 提交于 2020-03-05 13:54:31

@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@

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!