1. 程式人生 > 實用技巧 >網路流 拆點+最小割點集

網路流 拆點+最小割點集

P4662 [BalticOI 2008]黑手黨

題目描述

Byteland 國警方收到了一條匿名舉報,其中說當地黑幫老大正計劃一次從港口到郊區倉庫的運輸。警方知道運輸的時間並且知道運輸需要用到國家的高速公路網。

高速公路網包含雙向的高速公路段,每個路段直接連著兩個不同的收費站。一個收費站可能與很多其他的收費站相連。汽車只能通過收費站進入或離開高速公路網。據所知,黑幫會距港口邊最近的收費站進入高速公路,從距倉庫最近的收費站離開(不會在出高速後重新進入)。特警組位於選定的收費站處。當運輸車輛進入被監控的收費站時,它就會被警察抓住。

從這個角度看,最簡單的辦法就是在每個收費站處都安排特警班。然而,控制一個收費站需要特定的費用,每個收費站費用不同。警方想要讓花費最小,所以他們需要制定一個收費站的最小控制集,這個集合滿足兩個條件:

所有從港口到倉庫的交通必須至少經過集合中的一個收費站
監控這些收費站的費用(即監控每一個收費站費用之和)最小
你可以假設使用高速公路可以從港口到倉庫。

任務

寫一個程式能夠:

從標準輸入中讀取高速公路網,監控代價和運輸的起點和終點
找到收費站的最小控制集
輸出這個集合到標準輸出

輸入格式

標準輸入的第一行包含兩個整數 n 和 m,表示收費站的總數和公路段的總數。收費站按 1 到 n 標號;

第二行包含兩個整數 a 和 b,分別表示距港口和倉庫最近的兩個收費站編號;

接下來 n 行表示控制費用,第 i 行包含一個整數,表示第 i 個收費站的控制費用 c;

接下來 m 行表示高速公路網,第 j 行包含兩個整數 x 和 y,表示在 x 和 y 收費站之間有一條公路段相連。每一條高速公路段只出現一次。

輸出格式

唯一的一行輸出應包含最小控制集中收費站的編號,以遞增順序輸出,用一個空格分隔。

如果有多於一個最小控制集,你的程式可以輸出任意一個。

輸入

5 6
5 3
2
4
8
3
10
1 5
1 2
2 4
4 5
2 3
3 4

輸出

1 4

思路

拆點之後。把費用當流量,就是最小割。
我們在殘餘網路DFS。不經過滿流的邊,那麼如果一個點拆的2個點一個在S集合。一個在T集合。那麼這個點就是選擇的。因為源匯可以拆
我們應該建立虛的源匯。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 10;
const int maxm= 1e6+ 10;
const int INF = 0x3f3f3f3f;

//註釋為弧優化
struct max_Folw {
    int d[maxn], cur[maxn], start, tend;
    int Q[maxn * 2];
    struct node {
        int to, cap, flow, next;
    } edge[maxm << 1];

    int head[maxn];
    bool vis[maxn];
    int cnt;

    void init(int s, int t){
        memset(head, -1, sizeof(head));
        cnt=0;
        start=s, tend=t;
    }

    void add(int start, int to, int cap) {
        edge[cnt] = {to, cap, 0, head[start]};
        head[start] = cnt++;
    }

    void AddEdge(int start, int to, int cap){
        add(start, to, cap);
        add(to, start, 0);
    }

    bool BFS() {
        memset(d, -1, sizeof(d));
        int Thead, Ttail;
        Thead = Ttail = 0;
        Q[Ttail++] = tend;
        d[tend] = 0;
        while (Thead<Ttail) {
            int x = Q[Thead];
            if (x == start)
                return true;
            for (int i = head[x]; i != -1; i = edge[i].next) {
                int temp = edge[i].to;
                if (d[temp] == -1 && edge[i^1].cap > edge[i^1].flow) { //沒有標記,且可行流大於0
                    d[temp] = d[x] + 1;
                    Q[Ttail++] = temp;
                }
            }
            Thead++;
        }
        return false;//匯點是否成功標號,也就是說是否找到增廣路
    }

    int DFS(int x, int cap) {
        if (x == tend)
            return cap;
        int flow = 0, f;
        //for (int i = cur[x]; i != -1; i = edge[cur[x]=i].next) {
        for (int i = head[x]; i != -1; i = edge[i].next) {
            int temp = edge[i].to;
            if (d[temp] == d[x] - 1 && edge[i].cap > edge[i].flow) {
                f = DFS(temp, min(cap - flow, edge[i].cap - edge[i].flow));
                edge[i].flow += f;
                edge[i ^ 1].flow -= f;
                flow += f;
                if (flow == cap)
                    return flow;
            }
        }
        d[x] = -2;//防止重搜
        return flow;
    }

    int maxflow() {
        int flow = 0, f;
        while (BFS()) {
            //memcpy(cur, head, sizeof head);
            flow += DFS(start, INF);
        }
        return flow;
    }

    //得到割點集合
    queue<int> q;
    void bfs(){
        memset(vis, 0, sizeof(vis));
        vis[start]=1;
        q.push(start);
        while(!q.empty()){
            int u=q.front(); q.pop();
            for(int i = head[u]; i != -1; i = edge[i].next){
                int to=edge[i].to, w=edge[i].cap-edge[i].flow;
                if(w&&!vis[to]){
                    vis[to]=1;
                    q.push(to);
                }
            }
        }
    }

}flow;
//edge[i].cap-edge[i].flow==0 容量-流量=可流的流量 =0說明已經滿流
int a[maxn];
set<int> st;
int main() {

    int n, m, A, B; scanf("%d%d%d%d",&n, &m, &A, &B);
    flow.init(0, 2*n+5);
    flow.AddEdge(0, A, INF);
    flow.AddEdge(B+n, 2*n+5, INF);
    for(int i=1; i<=n; i++){
        scanf("%d", &a[i]);
        flow.AddEdge(i, i+n, a[i]);
    }
    for(int i=1; i<=m; i++){
        int x, y; scanf("%d%d", &x, &y);
        flow.AddEdge(x+n, y, INF);
        flow.AddEdge(y+n, x, INF);
    }
    flow.maxflow();
    flow.bfs();
    for(int i=1; i<=n; i++){
        if((flow.vis[i]&&!flow.vis[i+n])||(!flow.vis[i]&&flow.vis[i+n])){
            st.insert(i);
        }
    }
    for(auto x: st){
        printf("%d ", x);
    }
    printf("\n");

    return 0;
}