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 0≤ai,j≤106),这个值越大,表示这一小块巧克力越美味。
正当明明咽了咽口水,准备享用美味时,舟舟神奇地出现了。看到舟舟恳求的目光,明明决定从中选出一些小块与舟舟一同分享。
舟舟希望这些被选出的巧克力是连通的(两块巧克力连通当且仅当他们有公共边),而且这些巧克力要包含至少 k k k( 1 ≤ k ≤ 5 1\le k\le 5 1≤k≤5)种。而那些被挤压过的巧克力则是不能被选中的。
明明想满足舟舟的愿望,但他又有点「抠」,想将美味尽可能多地留给自己。所以明明希望选出的巧克力块数能够尽可能地少。如果在选出的块数最少的前提下,美味值的中位数(我们定义 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 1≤T≤5),表示测试数据组数。
对于每组测试数据:
输入第一行包含三个正整数 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,1≤m≤233 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 1≤ci,j≤n×m A \text{A} A 2 1 ≤ n × m ≤ 20 1\le n\times m\le 20 1≤n×m≤20 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 1≤ci,j≤n×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 1≤ci,j≤n×m A \text{A} A 5~6 1 ≤ n × m ≤ 30 1\le n\times m\le 30 1≤n×m≤30 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 1≤ci,j≤n×m A \text{A} A 7~9 1 ≤ n × m ≤ 50 1\le n\times m\le 50 1≤n×m≤50 c i , j = − 1 c_{i,j}=-1 ci,j=−1 或 1 ≤ c i , j ≤ 8 1\le c_{i,j}\le8 1≤ci,j≤8 A \text{A} A 10 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1≤n×m≤233 c i , j = − 1 c_{i,j}=-1 ci,j=−1 或 1 ≤ c i , j ≤ 8 1\le c_{i,j}\le8 1≤ci,j≤8 A \text{A} A 11~12 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1≤n×m≤233 c i , j = − 1 c_{i,j}=-1 ci,j=−1 或 1 ≤ c i , j ≤ 8 1\le c_{i,j}\le8 1≤ci,j≤8 B \text{B} B 13~15 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1≤n×m≤233 c i , j = − 1 c_{i,j}=-1 ci,j=−1 或 1 ≤ c i , j ≤ 14 1\le c_{i,j}\le14 1≤ci,j≤14 B \text{B} B 16~20 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1≤n×m≤233 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 1≤ci,j≤n×m B \text{B} B 21 1 ≤ n × m ≤ 233 1\le n\times m\le 233 1≤n×m≤233 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 1≤ci,j≤n×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∈[1∼5]。
这意味着我们能乱搞(随机化大法)!
我们将每种颜色随机映射到 [ 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;
}