No Abstract.

Description

有$n \times m$顆巧克力, 排成矩陣, 每顆有一個顏色和價值, 請你選出盡量少的巧克力, 使得至少包含$k$種不同顏色, 在此基礎上, 使價值中位數最小.

Solution

這什麼神仙解法啊……

首先, 如果拋開中位數的限制, 這就是一個斯坦納樹. 但是顏色很不好搞, 因爲顏色有點多, 考慮到用上的不多, 我們對每個顏色隨機地做$[1,k]$的映射, 求包含五種狀態的斯坦納數. 這樣是有$\frac{5!}{5^5}$的正確率的, 所以做那麼一百多輪斯坦納樹就好啦.

然後考慮二分中位數. 這裏有個奇技淫巧的說… 把小於等於當前答案的$val$都設爲$999$, 反之是$1001$. 因爲點數最多$233$個, 所以我們放大的範圍的一半還是比點數多, 這樣上取整就不會擔心誤差啦. 實際的點數就是$\frac{ans + 500}{1000}$. 然後如果二分的答案小於點數 $\times 1000$的話, 就說明答案還可以更小. 繼續二分過去就好了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <bits/stdc++.h>

using namespace std;

const int maxn = 250;
const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, 1, 0, -1};

int n, m, k, col[maxn], ori[maxn][maxn], orv[maxn][maxn];
int f[maxn][70], val[maxn];
int mp[maxn];

inline int rd() {
register int x = 0, f = 0, c = getchar();
while (!isdigit(c)) {
if (c == '-') f = 1;
c = getchar();
}
while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
return f ? -x : x;
}
struct edge {
int to, nxt;
} e[maxn << 3];
int ptr, lnk[maxn], vis[maxn];
int id[maxn][maxn], cnt, tmp;
queue<int> q;

inline void add(int bgn, int end) {
e[++ptr] = (edge){end, lnk[bgn]};
lnk[bgn] = ptr;
}
inline void spfa(int s) {
while (!q.empty()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (int p = lnk[u]; p; p = e[p].nxt) {
int y = e[p].to;
if (!(~col[y])) continue;
if (f[y][s] > f[u][s] + val[y]) {
f[y][s] = f[u][s] + val[y];
if (!vis[y])
vis[y] = 1, q.push(y);
}
}
}
}
inline void solve() {
//queue<int> q;
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n * m; ++i)
if (~col[i])
f[i][1 << col[i]] = val[i];
int lim = (1 << k) - 1;
for (int s = 1; s <= lim; ++s) {
for (int i = 1; i <= n * m; ++i)
for (int sn = s - 1; sn; sn = (sn - 1) & s)
f[i][s] = min(f[i][s], f[i][s ^ sn] + f[i][sn] - val[i]);
for (int i = 1; i <= n * m; ++i)
if (f[i][s] != 0x3f3f3f3f)
vis[i] = 1, q.push(i);
spfa(s);
}
for (int i = 1; i <= n * m; ++i)
tmp = min(tmp, f[i][lim]);
}

int main() {
srand(time(NULL));
int T = rd();
while (T--) {
ptr = 0; cnt = 0;
memset(lnk, 0, sizeof lnk);
n = rd(); m = rd(); k = rd();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
id[i][j] = ++cnt;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
ori[i][j] = rd();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
orv[i][j] = rd();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
for (int k = 0; k < 4; ++k) {
int ix = i + dx[k], iy = j + dy[k];
if (ix >= 1 && ix <= n && iy >= 1 && iy <= m)
add(id[i][j], id[ix][iy]);
}
int l = 0, r = 1e+6 + 5, pnt;
bool nosolu = false;
while (l <= r) {
//random
int mid = (l + r) >> 1, ans = 0x3f3f3f3f;
for (int CNT_ = 1; CNT_ < 233; ++CNT_) {
tmp = 0x3f3f3f3f;
for (int i = 1; i <= n * m; ++i)
mp[i] = rand() % k;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
col[id[i][j]] = (ori[i][j] == -1 ? -1 : mp[ori[i][j]]),
val[id[i][j]] = (orv[i][j] <= mid ? 999 : 1001);
solve();
ans = min(ans, tmp);
}
if (ans == 0x3f3f3f3f) {
nosolu = true;
break;
}
int trans = (ans + 500) / 1000;
if (ans <= trans * 1000)
r = mid - 1, pnt = trans;
else
l = mid + 1;
}
if (nosolu) puts("-1 -1");
else printf("%d %d\n", pnt, r + 1);
}
return 0;
}