牛客周赛 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;
}
posted @ 2024-10-28 23:52  兀十三  阅读(83)  评论(0)    收藏  举报