題目描述
https://ac.nowcoder.com/acm/contest/92972/D
小紅是一名醫生。
現在小紅對於每個病人的症狀用一個長度為𝑚的01串表示,第𝑖個字元代表第𝑖個身體部位的症狀,0代表健康,1代表不健康。
一共有𝑘種藥,每種藥也用一個長度為𝑚的01串表示,第𝑖個字元為'1'代表該藥可以治癒第𝑖個部位的症狀。
對於每個病人,請你幫小紅求出治癒該病人需要開的最少的藥數量。
輸入描述:
第一行輸入兩個正整數𝑛,𝑚,代表病人數量和症狀種類數。
接下來的𝑛行,每行輸入一個長度為𝑚的01串,代表每個病人的症狀情況。
接下來一行輸入一個正整數𝑘,代表藥物的數量。
接下來的𝑘行,每行輸入一個長度為𝑚的01串,代表每個藥物可以治癒的症狀情況。
1 ≤ 𝑚 ≤ 20
1 ≤ 𝑛 ≤ \(10^4\)
1 ≤ 𝑘 ≤ 10
輸出描述:
輸出n行,每行輸出治癒該病人所需的最小藥物數量。特殊的,如果該病人無法被治癒,請輸出-1。
題解
注意到長度m很小,只有20,考慮用二進位制壓縮狀態,1 << 20 = 1048576,不會超出陣列可儲存的最大範圍。我們先將讀入的症狀和藥能夠治癒的症狀即所有01串看作二進位制數儲存起來,接著列舉藥和所有症狀的情況(1 << m),將該症狀與使用當前藥治癒後的症狀情況連邊(如果藥無法治癒當前症狀的任何一個部位是不連邊的,會出現自環),因此定義0為完全治癒的情況。是否可達可以用dfs跑一遍判斷是否連通,也可以直接用最短路的dist陣列判斷距離是否更新。最後只需要倒著從終點0開始跑一遍最短路,即可得出每種症狀治癒的最小代價。時間複雜度為\(O(nlog(k*(1<<m)))\)。
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 2e6, M = 30;
int n, m, k, x;
int p[N], q[N];
vector<int> g[N];
bool st[N];
int id[N];
int cnt;
ll dist[N];
// dfs求連通塊
void dfs(int u) {
st[u] = true;
id[u] = cnt;
for (int i = 0; i < g[u].size(); ++i)
if (!st[g[u][i]])
dfs(g[u][i]);
}
// 判斷是否連通
inline bool isConnected(int a, int b) {
return id[a] == id[b];
}
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
dist[0] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 0});
while (heap.size()) {
PII k = heap.top();
heap.pop();
int ver = k.second;
ll distance = k.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = 0; i < g[ver].size(); ++i) {
if (dist[g[ver][i]] > distance + 1) {
dist[g[ver][i]] = distance + 1;
heap.push({dist[g[ver][i]], g[ver][i]});
}
}
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%1d", &x);
p[i] += x << (m - j);
}
}
scanf("%d", &k);
for (int i = 1; i <= k; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%1d", &x);
q[i] += x << (m - j);
}
}
for (int i = 1; i <= k; ++i) { // 列舉藥
for (int j = 0; j <= 1 << m; ++j) { // 列舉症狀
if ((j | q[i]) != j + q[i]) // 不是互補的情況
//g[j].push_back((j ^ q[i]) & j);
g[(j ^ q[i]) & j].push_back(j);
}
}
memset(st, 0, sizeof st);
dijkstra();
for (int i = 1; i <= n; ++i) {
if (dist[p[i]] >= 1e9)
printf("-1\n");
else
printf("%lld\n", dist[p[i]]);
}
return 0;
}
因為涉及到狀態轉移,所以用dp做也是一樣的。只需要對程式碼略做更改即可。定義\(f[j]\)為考慮前i種藥,治癒症狀\(j\)所需藥的最少數量,則狀態轉移方程為
f[j] = min(f[j], f[(j ^ q[i]) & j] + 1);
與最短路建立有向邊的方法相同,當前症狀應該是由用該藥後治癒的症狀情況倒推過來。
時間複雜度為\(O(k*(1<<m))\)。
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 2e6, M = 30;
int n, m, k, x;
int p[N], q[N];
int f[N];
// f[i] : 治癒症狀i所需藥的最少數量
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%1d", &x);
p[i] += x << (m - j);
}
}
scanf("%d", &k);
for (int i = 1; i <= k; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%1d", &x);
q[i] += x << (m - j);
}
}
memset(f, 0x3f, sizeof f);
f[0] = 0;
for (int i = 1; i <= k; ++i) { // 列舉藥
for (int j = 0; j <= 1 << m; ++j) { // 列舉症狀
if ((j | q[i]) != j + q[i])
f[j] = min(f[j], f[(j ^ q[i]) & j] + 1);
}
}
for (int i = 1; i <= n; ++i) {
if (f[p[i]] >= 1e9)
printf("-1\n");
else
printf("%d\n", f[p[i]]);
}
return 0;
}