1. 程式人生 > 其它 >AcWing 1192. 獎金 [一題三解]

AcWing 1192. 獎金 [一題三解]

題目傳送門

一、拓撲排序+遞推極值

#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 20010;

int n, m;
int d[N];
int dist[N];
//鄰接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
vector<int> path; //拓撲序路徑
bool topsort() {
    queue<int> q;
    for (int i = 1; i <= n; i++)
        if (!d[i]) q.push(i);
    int cnt = 0;
    while (q.size()) {
        int t = q.front();
        q.pop();
        path.push_back(t);
        cnt++;
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (--d[j] == 0)
                q.push(j);
        }
    }
    return cnt == n; //是否有拓撲序
}

int main() {
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        add(b, a);
        d[a]++;
    }
    if (!topsort()) //不存在拓撲序
        puts("Poor Xed");
    else {
        for (int i = 1; i <= n; i++) dist[i] = 100; //模擬建立超級源點,到每個點的距離是100
        for (int i = 0; i < n; i++) {               //遍歷拓撲序
            int j = path[i];
            for (int k = h[j]; ~k; k = ne[k])              //通過點找到所有出邊
                                                           //後序必須比前序大,最少是大1個
                dist[e[k]] = max(dist[e[k]], dist[j] + 1); //遞推求最長路
        }
        //求最少總獎金
        int res = 0;
        for (int i = 1; i <= n; i++) res += dist[i];
        printf("%d\n", res);
    }
    return 0;
}

二、差分約束(spfa求最長路+判斷正環)

#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 30010;
int din[N];  //記錄每個節點入度
int dist[N]; //記錄每個節點距離起點的最小值
int cnt[N];  // cnt[i]記錄以節點i為終點的路徑的邊數
bool st[N];
queue<int> q;
int n, m;
//鄰接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

bool spfa() {
    memset(dist, -0x3f, sizeof dist);
    q.push(0);
    st[0] = true;
    dist[0] = 0;

    while (q.size()) {
        int t = q.front();
        q.pop();
        st[t] = false;

        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[j] < dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                //判斷是不是存在正環
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n + 1) return false;
                if (!st[j]) {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return true;
}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        int a, b;
        cin >> a >> b;
        add(b, a, 1); /**  a >= b + 1 */
    }

    //建一個超級源點0號點
    for (int i = 1; i <= n; ++i) /** a >= xo + 100 */
        add(0, i, 100);

    if (spfa()) {
        int res = 0;
        for (int i = 1; i <= n; ++i) res += dist[i];
        cout << res << endl;
    } else
        puts("Poor Xed");
    return 0;
}

三、強連通分量縮點之後遞推求最長路

#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 60010;

int dnt[N], low[N];
bool in_sta[N]; //記錄是否在棧中
int dist[N];    //記錄強連通分量距離起點的距離
stack<int> sta;
int id[N];       //記錄每個節點的強連通分量的編號
int scc_size[N]; //記錄強連通分量的節點個數
int timestamp;
int scc_cnt; //記錄強連通分量的編號
int n, m;

int h[N], hs[N], ne[M], e[M], w[M], idx; // hs[u]為強連通分量建的圖的表頭
void add(int h[], int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void tarjan(int u) {
    low[u] = dnt[u] = ++timestamp;
    sta.push(u);
    in_sta[u] = true;

    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dnt[j]) {
            tarjan(j);
            //有可能存在反向邊遍歷回祖宗節點
            low[u] = min(low[u], low[j]);
        } else if (in_sta[j])
            //有可能存在橫向邊和遍歷回之前遍歷過的節點
            low[u] = min(low[u], dnt[j]);
    }

    if (low[u] == dnt[u]) {
        int y;
        ++scc_cnt;
        do {
            y = sta.top();
            sta.pop();
            id[y] = scc_cnt;
            in_sta[y] = false;
            scc_size[scc_cnt]++;
        } while (y != u);
    }
}

int main() {
    memset(h, -1, sizeof h);
    memset(hs, -1, sizeof hs);
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        int a, b;
        cin >> a >> b;
        add(h, b, a, 1); /**  a >= b + 1 */
    }

    //建一個超級源點0號點
    for (int i = 1; i <= n; ++i) /** a >= xo + 100 */
        add(h, 0, i, 100);

    for (int i = 0; i <= n; ++i)
        if (!dnt[i]) tarjan(i);

    //縮點(列舉圖中所有每兩個節點,來建圖)
    bool success = true;
    for (int i = 0; i <= n; ++i) {
        for (int j = h[i]; ~j; j = ne[j]) {
            int k = e[j];

            int a = id[i], b = id[k];
            if (a == b) {
                if (w[j]) { //同一個強連通分量內的邊的權值只能為正數,否則存在正環
                    success = false;
                    break;
                }
            } else
                add(hs, a, b, w[j]);
        }
        if (!success) break;
    }

    if (!success)
        puts("Poor Xed");
    else {
        dist[0] = 0;
        //遞推求出新建的圖中的最長路(按照拓撲序來遞推,scc_cnt ~ 1這個順序符合拓撲序)
        for (int i = scc_cnt; i >= 1; i--) {
            //列舉i鄰接的所有的邊,找出最大的狀態轉移
            for (int j = hs[i]; ~j; j = ne[j]) {
                int k = e[j];
                dist[k] = max(dist[k], dist[i] + w[j]);
            }
        }
        int res = 0;
        for (int i = 1; i <= scc_cnt; ++i) res += (scc_size[i] * dist[i]);
        cout << res << endl;
    }
    return 0;
}