牛客周賽 Round 65(D)

兀十三發表於2024-10-28

題目描述

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;
}

相關文章