牛客周赛 Round 65(D)
题目描述
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;
}

浙公网安备 33010602011771号