Graph theory 0x05 【网络流】

最大流

基本问题,基于有向图。

  • 流量守恒
  • 反对称性
  • 容量限制

算法

  • “增广路”算法。Edmonds-Karp、Dinic、ISAP
  • “预流推进”算法。HLPP

Ford-Fulkerson方法

数据结构课讲的那个。重点在于residual network和其反向路径

Edmonds-Karp

最简单,复杂度高,小图,因此直接邻接矩阵。
O ( V E 2 ) O(VE^2) O(VE2)
HDU 1532 Drainage Ditches Edmonds-Karp模板

#include<bits/stdc++.h>
using namespace std;
const int maxn = 202;
const int inf = 0x7f7f7f;
int graph[maxn][maxn],pre[maxn],n,m;
int bfs(int s,int t){
    int flow[maxn];
    memset(pre,-1,sizeof(pre));
    flow[s]=inf;pre[s]=0;
    queue<int>Q; Q.push(s);
    while(!Q.empty()){
        int u=Q.front();Q.pop();
        if(u==t) break;  // 找到一条路径就跑
        for(int i=1;i<=m;++i){
            if(i!=s&&graph[u][i]>0&&pre[i]==-1){
                pre[i]=u;
                Q.push(i);
                flow[i]=min(flow[u],graph[u][i]);
            }
        }
    }
    if(pre[t]==-1) return -1;
    return flow[t];
}

int maxflow(int s,int t){
    int maxflow = 0;
    while(1){
        int flow = bfs(s,t);
        if(flow==-1) break;
        int cur = t;
        while(cur!=s){  // 更新残留网络
            int father = pre[cur];
            graph[father][cur]-=flow;
            graph[cur][father]+=flow;
            cur=father;
        }
        maxflow += flow;
    }
    return maxflow;
}
// 有向有环图,求最大流。(模板题)
int main(){
    while(~scanf("%d%d",&n,&m)){
        memset(graph,0,sizeof(graph));
        for(int i=1;i<=n;++i){
            int u,v,val;scanf("%d%d%d",&u,&v,&val);
            graph[u][v]+=val;  // 重边的可能
        }
        printf("%d\n",maxflow(1,m));
    }
}

Dinic

层次图阻塞流、多路增广。
HDU 3549 Flow Problem 最大流模板题

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

const int maxn = 1002;
const int INF = 0x7f7f7f7f;
// lrj的板子
struct Dinic{
    int n,m,s,t;
    struct Edge{
        int from,to,cap,flow;
    };
    vector<Edge> edges;
    vector<int> G[maxn];
    void AddEdge(int from,int to,int cap){
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,0,0});
        m = edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    };
    bool vis[maxn];
    int d[maxn];
    int cur[maxn];
    bool BFS() {
        memset(vis,0,sizeof(vis));
        queue<int> Q;
        Q.push(s);
        d[s]=0;
        vis[s]=1;
        while(!Q.empty()) {
            int x= Q.front();Q.pop();
            for(int i=0;i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]&&e.cap>e.flow) {
                    vis[e.to]=1;
                    d[e.to]=d[x]+1;
                    Q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    int DFS(int x,int a){
        if(x==t||a==0) return a;
        int flow = 0,f;
        for(int& i=cur[x];i<G[x].size();++i){
            Edge& e = edges[G[x][i]];
            if(d[x]+1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0) {
                e.flow += f;
                edges[G[x][i]^1].flow -= f;
                flow += f;
                a -= f;
                if(a==0) break;
            }
        }
        return flow;
    }
    int Maxflow(int s,int t){
        this->s=s;this->t=t;
        int flow=0;
        while(BFS()){
            memset(cur,0,sizeof(cur));
            flow += DFS(s,INF);
        }
        return flow;
    }
};
int cas=0;
void solve(){
    Dinic cur;
    int n,m;cin>>n>>m;
    cur.n=n,cur.m=m;
    for(int i=1;i<=m;++i) {
        int x,y,c;scanf("%d%d%d",&x,&y,&c);
        cur.AddEdge(x,y,c);
    }
    printf("Case %d: %d\n",++cas,cur.Maxflow(1,n));
}

int main(){
    int t;cin>>t;
    while(t--) solve();
}

ISAP

优化Dinic,不用重复标记深度(单词BFS)。每次用邻接点更改回退边(增广失败的边)的点的d值。+gap优化。while实现DFS,防止爆栈。
HDU 4280 Island Transport ISAP模板题
数据范围2<=N,M<=100,000。无向图。
无向图:把边改成有向边,来回方向cap都为val即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100004;
const int inf = 0x7f7f7f7f;
vector<int>G[maxn];  // 放进ISAP会爆空间。。

struct ISAP {
    int n,m,s,t;
    struct Edge{
        int from,to,cap,flow;
    };
    vector<Edge> edges;
    void AddEdge(int from,int to,int cap){
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,cap,0});
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    int p[maxn];
    int num[maxn];
    int d[maxn];
    int cur[maxn];
    int vis[maxn];
    int BFS(){
    	memset(vis,0,sizeof(vis));
        memset(d,0,sizeof(d));
        queue<int>Q;
        Q.push(t);
        d[t]=0;
        vis[t]=1;
        while(!Q.empty()){
            int x=Q.front();Q.pop();
            for(int i=0;i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]){
                	vis[e.to]=1;
                    d[e.to]=d[x]+1;
                    Q.push(e.to);
                }
            }
        }
        return d[s];
    }
    // while循环实现dfs
    int Augment() {
        int x=t,a=inf;
        while(x!=s){
            Edge& e = edges[p[x]];
            a=min(a,e.cap-e.flow);
            x=edges[p[x]].from;
        }
        x=t;
        while(x!=s){
            edges[p[x]].flow += a;
            edges[p[x]^1].flow -= a;
            x=edges[p[x]].from;
        }
        return a;
    }
    int Maxflow(int s,int t){
        this->s=s,this->t=t;
        int flow=0;
        if(!BFS()) return 0;
        memset(num,0,sizeof(num));
        for(int i=1;i<=n;++i) num[d[i]]++;
        int x=s;
        memset(cur,0,sizeof(cur));
        while(d[s]<n){
            // for 循环实现dfs
            if(x==t){
                flow += Augment();
                x=s;
            }
            int ok=0;
            for(int i=cur[x];i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(e.cap>e.flow&&d[x]==d[e.to]+1) { // advance
                    ok=1;
                    p[e.to]=G[x][i];
                    cur[x]=i;
                    x=e.to;
                    break;
                }
            }
            if(!ok) { // retreat
                int mn=n-1;
                for(int i=0;i<G[x].size();++i){
                    Edge& e = edges[G[x][i]];
                    if(e.cap>e.flow) mn=min(mn,d[e.to]);
                }
                if(--num[d[x]]==0) break;  // gap
                num[d[x]=mn+1]++;
                cur[x]=0;
                if(x!=s) x=edges[p[x]].from;
            }
        }
        return flow;
    }
};

void solve(){
    int n,m;scanf("%d%d",&n,&m);
    int minx=inf,idmin,idmax,maxx=-1;
    for(int i=1;i<=n;++i){
        int x,y;scanf("%d%d",&x,&y);
        if(x<minx) idmin=i,minx=x;
        if(x>maxx) idmax=i,maxx=x;
    }
    for(int i=1;i<=n;++i) G[i].clear();
    ISAP cur;
    cur.n=n;
    cur.s=idmin,cur.t=idmax;
    for(int i=1;i<=m;++i){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        cur.AddEdge(x,y,z);
    }
    printf("%d\n",cur.Maxflow(cur.s,cur.t));
}

int main(){
    int t;cin>>t;
    while(t--) solve();
}

HDU 1956 Sightseeing tour 混合图的欧拉回路
先判断连通性,这道题题目保证连通。
将无向边任意指一个方向,统计所有点的度数。出现奇数度点则不存在欧拉回路。
度数定义:出度-入度。
新建两个节点,s=1和t=n+2,把中间点往后挪一位。s指向所有度数>0的点,边权为度数/2。所有度数<0的点指向t,边权为-度数/2。
跑最大流。如果s出去的所有边满流,存在欧拉回路;否则不存在。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 204;
const int inf = 0x7f7f7f7f;
vector<int>G[maxn];

struct ISAP {
    int n,m,s,t;
    struct Edge{
        int from,to,cap,flow;
    };
    vector<Edge> edges;
    void AddEdge(int from,int to,int cap){
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,0,0});
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    int p[maxn];
    int num[maxn];
    int d[maxn];
    int vis[maxn];
    int cur[maxn];
    int BFS(){
        memset(d,0,sizeof(d)); // 不能将其初始化为-1。因为后面有num[d[i]],会出现未知错误!(找bug找了巨久)血的教训
        memset(vis,0,sizeof(vis));
        queue<int>Q;
        Q.push(t);
        d[t]=0;
        vis[t]=1;
        while(!Q.empty()){
            int x=Q.front();Q.pop();
            for(int i=0;i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]){
                    d[e.to]=d[x]+1;
                    vis[e.to]=1;
                    Q.push(e.to);
                }
            }
        }
        return d[s];
    }
    int Augment() {
        int x=t,a=inf;
        while(x!=s){
            Edge& e = edges[p[x]];
            a=min(a,e.cap-e.flow);
            x=edges[p[x]].from;
        }
        x=t;
        while(x!=s){
            edges[p[x]].flow += a;
            edges[p[x]^1].flow -= a;
            x=edges[p[x]].from;
        }
        return a;
    }
    int Maxflow(){
        int flow=0;
        if(!BFS()) return 0;
        memset(num,0,sizeof(num));
        for(int i=1;i<=n;++i) num[d[i]]++;
        int x=s;
        memset(cur,0,sizeof(cur));
        while(d[s]<n){
            if(x==t){
                flow += Augment();
                x=s;
            }
            int ok=0;
            for(int i=cur[x];i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(e.cap>e.flow&&d[x]==d[e.to]+1) { // advance
                    ok=1;
                    p[e.to]=G[x][i];
                    cur[x]=i;
                    x=e.to;
                    break;
                }
            }
            if(!ok) { // retreat
                int mn=n-1;
                for(int i=0;i<G[x].size();++i){
                    Edge& e = edges[G[x][i]];
                    if(e.cap>e.flow) mn=min(mn,d[e.to]);
                }
                if(--num[d[x]]==0) break;  // gap
                num[d[x]=mn+1]++;
                cur[x]=0;
                if(x!=s) x=edges[p[x]].from;
            }
        }
        return flow;
    }
};
int degree[maxn];
void solve(){
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1;i<=n+2;++i) G[i].clear();
    memset(degree,0,sizeof(degree));
    ISAP cur;cur.n=n+2,cur.s=1,cur.t=n+2;
    for(int i=1;i<=m;++i){
        int u,v,opt;scanf("%d%d%d",&u,&v,&opt);
        degree[++v]--;
        degree[++u]++;
        if(opt!=1) cur.AddEdge(u,v,1);
    }
    int sum=0;
    for(int i=2;i<=n+1;++i){
        if(abs(degree[i])&1) {cout<<"impossible"<<endl;return;}
        if(degree[i]>0) {
            cur.AddEdge(1,i,degree[i]/2);
            sum+=degree[i]/2;
        }
        if(degree[i]<0) cur.AddEdge(i,n+2,-degree[i]/2);
    }
    if(sum==cur.Maxflow()) cout<<"possible"<<endl;
    else cout<<"impossible"<<endl;
}

int main(){
    int t;cin>>t;
    while(t--) solve();
}

HDU 3472 HS BDC 混合图欧拉路
(1)判断连通性
(2)度数:两个点的度数是奇数:欧拉路
(3)将无向边连接。并多连一条两个奇数点之间的边。
(4)用欧拉回路的办法判断。
PS. 没看清输入是opt=1时是无向边,导致debug3小时。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 30;
const int inf = 0x7f7f7f7f;
vector<int>G[maxn];

struct DSU {
    int fa[maxn],vis[maxn];
    void init(int n){
        for(int i=1;i<=n;++i) fa[i]=i;
        memset(vis,0,sizeof(vis));
    }
    int find(int x){
        return fa[x]=(fa[x]==x?x:find(fa[x]));
    }
    void unite(int x,int y){
        vis[x]=vis[y]=1;
        x=find(x),y=find(y);
        if(x==y) return;
        fa[x]=y;
    }
    int num(int n){
        int cnt=0;
        for(int i=1;i<=n;++i)
            if(vis[i]&&fa[i]==i) cnt++;
        return cnt;
    }
};

struct ISAP {
    int n,m,s,t;
    struct Edge{
        int from,to,cap,flow;
    };
    vector<Edge> edges;
    void Addedge(int from,int to,int cap){
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,0,0});
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    int p[maxn];
    int num[maxn];
    int mp[maxn];
    int d[maxn];
    int cur[maxn];
    int vis[maxn];
    int BFS(){
        memset(vis,0,sizeof(vis));
        memset(d,0,sizeof(d));
        queue<int> Q;
        Q.push(t);
        d[t]=0;
        vis[t]=1;
        n=1;
        mp[n]=t;
        while(!Q.empty()){
            int x=Q.front();Q.pop();
            for(int i=0;i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]){
                    ++n;
                    vis[e.to]=1;
                    mp[n]=e.to;
                    d[e.to]=d[x]+1;
                    Q.push(e.to);
                }
            }
        }
        return d[s];
    }
    int Augment(){
        int x=t,a=inf;
        while(x!=s){
            Edge& e=edges[p[x]];
            a=min(a,e.cap-e.flow);
            x=edges[p[x]].from;
        }
        x=t;
        while(x!=s){
            edges[p[x]].flow+=a;
            edges[p[x]^1].flow-=a;
            x=edges[p[x]].from;
        }
        return a;
    }
    int Maxflow(int s,int t){
        this->s=s,this->t=t;
        int flow=0;
        if(!BFS()) return 0;
        memset(num,0,sizeof(num));
        for(int i=1;i<=n;++i) num[d[mp[i]]]++;
        int x=s;
        memset(cur,0,sizeof(cur));
        while(d[s]<n){
            if(x==t) flow+=Augment(),x=s;
            int ok=0;
            for(int i=cur[x];i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(e.cap>e.flow&&d[x]==d[e.to]+1) {
                    ok=1;
                    p[e.to]=G[x][i];
                    cur[x]=i;
                    x=e.to;
                    break;
                }
            }
            if(!ok){
                int mn=n-1;
                for(int i=0;i<G[x].size();++i){
                    Edge& e=edges[G[x][i]];
                    if(e.cap>e.flow) mn=min(mn,d[e.to]);
                }
                if(--num[d[x]]==0) break;
                num[d[x]=mn+1]++;
                cur[x]=0;
                if(x!=s) x=edges[p[x]].from;
            }
        }
        return flow;
    }
};

int degree[maxn];
int cas;
void solve(){
    int n;cin>>n;
    ISAP cur;DSU cur2;
    cur2.init(28);
    memset(degree,0,sizeof(degree));
    for(int i=1;i<=28;++i) G[i].clear();
    for(int i=1;i<=n;++i){
        int opt;string s;cin>>s;
        int u=s[0]-'a'+1,v=s[s.length()-1]-'a'+1;
        scanf("%d",&opt);
        degree[++u]++;
        degree[++v]--;
        cur2.unite(u,v);
        if(opt) cur.Addedge(u,v,1);
    }
    cout<<"Case "<<++cas<<": ";
    if(cur2.num(28)!=1) {cout<<"Poor boy!"<<endl;return;}
    int sum=0,s=inf,t=inf,count=0;
    for(int i=2;i<=27;++i){
        if(abs(degree[i])&1){
            if(count==0) s=i;
            else t=i;
            count++;
        }
        if(degree[i]>0) {
            cur.Addedge(1,i,degree[i]/2);
            sum+=degree[i]/2;
        }
        if(degree[i]<0)
            cur.Addedge(i,28,-degree[i]/2);
    }
    if(count!=2&&count!=0) {cout<<"Poor boy!"<<endl;return;}
    if(s!=inf&&t!=inf) {
        if (degree[s] < degree[t]) swap(s, t);
        cur.Addedge(t,s,1);
    }
    if(sum==cur.Maxflow(1,28)) cout<<"Well done!"<<endl;
    else cout<<"Poor boy!"<<endl;
}

int main(){
    int t;cin>>t;
    while(t--) solve();
}

【飞马杯】体育课排队
题意:坐标系,n个人,n个位置,曼哈顿距离。人移动速度为1.问最短需要多久,所有人到这些位置上。
思路:二分图匹配。然而问的是最小的最大权。普通二分图算法无法处理。考虑二分答案。每次加入权小于等于答案的边。然后问对该图是否构成匹配。二分图的算法会TLE。于是选择网络流ISAP算法。

#include<bits/stdc++.h>

using namespace std;
#define maxn 2005
#define maxm 2004005
#define INF 1234567890
int T, n, m, ss[maxn], sx[maxn], sy[maxn], sum[maxn], l, r, s, t, maxflow, cnt = 1,
        x[maxn], y[maxn], head[maxn], dis[maxn], cur[maxn], gap[maxn], ans[maxn];
struct Edge {
    int u, v, w, pre, next;
} edge[maxm];

inline void add(int u, int v, int w) {
    edge[++cnt].u = u;
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].pre = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
    return;
}

inline int Dfs(int u, int lim) {
    if (!lim || u == t) return lim;
    int flow = 0, f;
    for (int i = cur[u]; i; i = edge[i].next) {
        int v = edge[i].v, w = edge[i].w;
        cur[u] = i;
        if (dis[v] + 1 == dis[u] && (f = Dfs(v, min(lim, w)))) {
            flow += f;
            lim -= f;
            edge[i].w -= f;
            edge[i ^ 1].w += f;
            if (!lim) return flow;
        }
    }
    gap[dis[u]]--;
    if (!gap[dis[u]]) dis[s] = t + 1;
    dis[u]++;
    gap[dis[u]]++;
    return flow;
}

inline void Bfs() {
    queue<int> q;
    for (int i = 1; i <= t; i++) {
        dis[i] = INF;
        gap[i] = 0;
    }
    dis[t] = 0;
    gap[0] = 1;
    q.push(t);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].v;
            if (dis[v] >= INF) {
                dis[v] = dis[u] + 1;
                gap[dis[v]]++;
                q.push(v);
            }
        }
    }
    return;
}

inline void ISAP() {
    Bfs();
    while (dis[s] < t) {
        for (int i = 1; i <= t; i++)
            cur[i] = head[i];
        maxflow += Dfs(s, INF);
    }
}

inline bool Check(int mid) {
    cnt = 1;
    maxflow = 0;
    for (int i = 1; i <= t; i++)
        head[i] = dis[i] = cur[i] = gap[i] = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int k = 1; k <= ss[j]; k++) {
                int w = abs(sx[j] + k - 1 - x[i]) + abs(sy[j] - y[i]);
                if (w > mid) continue;
                add(i, n + sum[j - 1] + k, 1);
                add(n + sum[j - 1] + k, i, 0);
            }
    for (int i = 1; i <= n; i++) {
        add(s, i, 1);
        add(i, s, 0);
        add(i + n, t, 1);
        add(t, i + n, 0);
    }
    ISAP();
    if (maxflow == n) return 1;
    else return 0;
}

int main() {
    cin >> T;
    while (T--) {
        cin >> n >> m;
        l = 0, r = 5000;
        for (int i = 1; i <= n; i++)
            cin >> x[i] >> y[i];
        for (int i = 1; i <= m; i++) {
            cin >> ss[i] >> sx[i] >> sy[i];
            sum[i] = sum[i - 1] + ss[i];
        }
        s = 2 * n + 1;
        t = 2 * n + 2;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (Check(mid)) r = mid;
            else l = mid + 1;
        }
        cout << l << endl;
        Check(l);
        for (int i = 1; i <= n; i++)
            for (int j = head[i]; j; j = edge[j].next) {
                int v = edge[j].v, w = edge[j].w, pre = edge[j].pre;
                if (pre > w) ans[v - n] = i;
            }
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= ss[i]; j++)
                cout << ans[sum[i - 1] + j] << " \n"[j == ss[i]];
    }
    return 0;
}

最小割

HDU 3251 Being a Hero 最小割
题意:给n个城市,m条有向边。每条边有权值,如今有些城市能够选择得到。可选的城市有一个价值。可是要满足从1到达不了这些城市,为了满足要求能够去掉一些边,须要花费边的权值,问终于得到的最大价值是多少,并给出方案。(抄别人的题意)

思路:
(1)可选的城市连一条边去到一个新节点 t t t,权值为该城市的价值。
(2)那么最大价值=可选城市总价值-最大流。
(3)why?最大流后,图被分成两个部分,一个是包含 s s s 的部分,一个是包含 t t t 的部分。连通这两个部分的边的权值和为最大流,这些边就是最小割的割边。这些边中若存在可选城市指向 t t t 的边,说明这个可选城市不需要被选,否则,说明需要被选。才能成就最大流/最小割。也可以这样想:假设全部可选城市都选上。计算最小割,若某个城市连向终点的边被割掉了,那说明不要这个城市的价值,才能成就最小割。所以是总价值减去最大流。
(4)如何给出割边?在残留网络跑一次BFS,得到包含 s s s 的部分,剩下的点包含在另一部分,包含 t t t 的部分。连接这两个部分的边就是最小割。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000;
const int inf = 0x7f7f7f7f;
vector<int> G[maxn];
int is[maxn];
struct ISAP {
    int n,m,s,t;
    struct Edge{
        int from,to,cap,flow;
    };
    vector<Edge> edges;
    void Addedge(int from,int to,int cap){
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,0,0});
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    int p[maxn];
    int num[maxn];
    int d[maxn];
    int cur[maxn];
    int vis[maxn];
    int BFS(){
        memset(vis,0,sizeof(vis));
        memset(d,0,sizeof(d));
        queue<int> Q;
        Q.push(t);
        d[t]=0;
        vis[t]=1;
        while(!Q.empty()){
            int x=Q.front();Q.pop();
            for(int i=0;i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]){
                    vis[e.to]=1;
                    d[e.to]=d[x]+1;
                    Q.push(e.to);
                }
            }
        }
        return d[s];
    }
    int Augment(){
        int x=t,a=inf;
        while(x!=s){
            Edge& e=edges[p[x]];
            a=min(a,e.cap-e.flow);
            x=edges[p[x]].from;
        }
        x=t;
        while(x!=s){
            edges[p[x]].flow+=a;
            edges[p[x]^1].flow-=a;
            x=edges[p[x]].from;
        }
        return a;
    }
    int Maxflow(int s,int t){
        this->s=s,this->t=t;
        int flow=0;
        if(!BFS()) return 0;
        memset(num,0,sizeof(num));
        for(int i=1;i<=n;++i) num[d[i]]++;
        int x=s;
        memset(cur,0,sizeof(cur));
        while(d[s]<n){
            if(x==t) flow+=Augment(),x=s;
            int ok=0;
            for(int i=cur[x];i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(e.cap>e.flow&&d[x]==d[e.to]+1) {
                    ok=1;
                    p[e.to]=G[x][i];
                    cur[x]=i;
                    x=e.to;
                    break;
                }
            }
            if(!ok){
                int mn=n-1;
                for(int i=0;i<G[x].size();++i){
                    Edge& e=edges[G[x][i]];
                    if(e.cap>e.flow) mn=min(mn,d[e.to]);
                }
                if(--num[d[x]]==0) break;
                num[d[x]=mn+1]++;
                cur[x]=0;
                if(x!=s) x=edges[p[x]].from;
            }
        }
        return flow;
    }
    void getedge(){
        memset(vis,0,sizeof(vis));
        vis[1]=1;
        queue<int> Q;
        Q.push(1);
        while(!Q.empty()) {
            int x = Q.front();
            Q.pop();
            for (int i = 0; i < G[x].size(); ++i) {
                Edge &e = edges[G[x][i]];
                if (!vis[e.to] && e.cap > e.flow) {
                    vis[e.to] = 1;
                    Q.push(e.to);
                }
            }
        }
        vector<int> ed;
        for(int i=0;i<m;i+=2){
            if(edges[i].to!=t&&vis[edges[i].from]&&!vis[edges[i].to])
                ed.push_back(i/2+1);
        }
        cout<<ed.size()<<" ";
        for(int i=0;i<ed.size()-1;++i) cout<<ed[i]<<" ";
        cout<<ed[ed.size()-1]<<endl;
    }
};
int cas=0;
void solve(){
    int n,m,f;cin>>n>>m>>f;
    ISAP cur;cur.n=n+1;
    for(int i=1;i<=n+1;++i) G[i].clear();
    memset(is,0,sizeof(is));
    for(int i=1;i<=m;++i){
        int u,v,c;scanf("%d%d%d",&u,&v,&c);
        cur.Addedge(u,v,c);
    }
    int sum=0;
    for(int i=1;i<=f;++i){
        int u,c;scanf("%d%d",&u,&c);
        is[u]=1;
        sum+=c;
        cur.Addedge(u,n+1,c);
    }
    int mincut=cur.Maxflow(1,n+1);
    cout<<"Case "<<++cas<<": "<<sum-mincut<<endl;
    cur.getedge();
}

int main(){
    int t;cin>>t;
    while(t--) solve();
}

POJ 1815 Friendship
题意:无向图。删最少的点使得S和T不连通。求删的点数,最小字典序输出点集。
思路:经典题。如果是删边那么直接最小割,原图原边边权为1就可以做出来。但是此处是删点,考虑拆点:把一个点拆成两个点:入和出。连一条边,从入点连到出点,边权为1。我们不希望原图的边被拆,则将那些边的边权设为inf。这样,跑最大流(最小割)出来,被删的就是连接原图的一个点出和入的边。也就是说,被删的就是点。然后如何输出最小字典序点集,考虑暴力,对每个点,尝试去掉他再重新建图跑最小割,如果最小割的值变小1,则当前点就被删了。注意:去掉这个点仅仅是去掉这个点内部的那条边,与之相关的边都会跑不了。然后再去掉第二个点,看最小割是否又小了1,以此类推。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#define in(x) x
#define out(x) (x+n)
using namespace std;
const int maxn = 403;
const int inf = 0x7f7f7f7f;
vector<int> G[maxn];
int A[maxn][maxn];
int isdel[maxn];

struct ISAP {
    int n,m,s,t;
    struct Edge{
        int from,to,cap,flow;
    };
    vector<Edge> edges;
    void AddEdge(int from,int to,int cap){
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,0,0});
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    void build(){
        edges.clear();
        for(int i=0;i<=2*n;++i) G[i].clear();
        for(int i=1;i<=n;++i)
            if(!isdel[i]) AddEdge(in(i),out(i),1);
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                if(A[i][j]) AddEdge(out(i),in(j),inf);
    }
    int p[maxn];
    int num[maxn];
    int d[maxn];
    int vis[maxn];
    int cur[maxn];
    int BFS(){
        memset(d,0,sizeof(d));
        memset(vis,0,sizeof(vis));
        queue<int>Q;
        Q.push(t);
        d[t]=0;
        vis[t]=1;
        while(!Q.empty()){
            int x=Q.front();Q.pop();
            for(int i=0;i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]){
                    d[e.to]=d[x]+1;
                    vis[e.to]=1;
                    Q.push(e.to);
                }
            }
        }
        return d[s];
    }
    int Augment() {
        int x=t,a=inf;
        while(x!=s){
            Edge& e = edges[p[x]];
            a=min(a,e.cap-e.flow);
            x=edges[p[x]].from;
        }
        x=t;
        while(x!=s){
            edges[p[x]].flow += a;
            edges[p[x]^1].flow -= a;
            x=edges[p[x]].from;
        }
        return a;
    }
    int Maxflow(int s,int t){
        this->s=s,this->t=t;
        int flow=0;
        if(!BFS()) return 0;
        memset(num,0,sizeof(num));
        for(int i=1;i<=2*n;++i) num[d[i]]++;
        int x=s;
        memset(cur,0,sizeof(cur));
        while(d[s]<2*n){
            if(x==t){
                flow += Augment();
                x=s;
            }
            int ok=0;
            for(int i=cur[x];i<G[x].size();++i){
                Edge& e=edges[G[x][i]];
                if(e.cap>e.flow&&d[x]==d[e.to]+1) { // advance
                    ok=1;
                    p[e.to]=G[x][i];
                    cur[x]=i;
                    x=e.to;
                    break;
                }
            }
            if(!ok) { // retreat
                int mn=2*n-1;
                for(int i=0;i<G[x].size();++i){
                    Edge& e = edges[G[x][i]];
                    if(e.cap>e.flow) mn=min(mn,d[e.to]);
                }
                if(--num[d[x]]==0) break;  // gap
                num[d[x]=mn+1]++;
                cur[x]=0;
                if(x!=s) x=edges[p[x]].from;
            }
        }
        return flow;
    }
};

int main(){
    int n,s,t;cin>>n>>s>>t;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            scanf("%d",&A[i][j]);
    if(A[s][t]) {cout<<"NO ANSWER!"<<endl;return 0;}
    ISAP cur;
    cur.n=n;
    cur.build();
    int ans=cur.Maxflow(out(s),in(t));
    cout<<ans<<endl;

    vector<int> res;
    for(int i=1;i<=n;++i){
        if(i==s) continue;
        if(i==t) continue;
        isdel[i]=1;
        cur.build();
        int newa = cur.Maxflow(out(s),in(t));
        if(newa==ans-1) res.push_back(i),ans--;
        else isdel[i]=0;
    }
    int sz=res.size();
    for(int i=0;i<sz-1;++i) cout<<res[i]<<" ";
    if(sz!=0) cout<<res[sz-1]<<endl;
}

最小费用最大流

POJ 2135 Farm Tour 费用流裸题
无向图:不可以像最大流那样一条边变两条正边。因为此题是无向图,所以建边的时候如果建两条费用都是正的边的话,退流时无法修正费用。
所以应该建4条边。

#include<iostream>
#include<vector>
#include<queue>
#include<string.h>
#include<cstdio>
using namespace std;
const int maxn = 1e3+4;
const int inf = 0x7f7f7f7f;
struct Edge {
    int from,to,cap,flow,cost;
    Edge(int u,int v,int c,int f,int w):from(u),to(v),cap(c),flow(f),cost(w){}
};

vector<int> G[maxn];

struct MCMF {
    int n,m;
    vector<Edge> edges;
    int inq[maxn];
    int d[maxn];
    int p[maxn];
    int a[maxn];

    void init(int n){
        this->n = n;
        for(int i=1;i<=n;++i) G[i].clear();
        edges.clear();
    }

    void AddEdge(int from,int to,int cap,int cost){
        edges.push_back(Edge{from,to,cap,0,cost});
        edges.push_back(Edge{to,from,0,0,-cost});
        m = edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }

    bool BellmanFord(int s,int t,int& flow,long long& cost) {
        for(int i=1;i<=n;++i) d[i]=inf;
        memset(inq,0,sizeof(inq));
        d[s] = 0;inq[s] = 1;p[s] = 0;a[s] = inf;

        queue<int> Q;
        Q.push(s);
        while(!Q.empty()){
            int u=Q.front();Q.pop();
            inq[u]=0;
            for(int i=0;i<G[u].size();++i){
                Edge& e = edges[G[u][i]];
                if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){
                    d[e.to]=d[u]+e.cost;
                    p[e.to]=G[u][i];
                    a[e.to]=min(a[u],e.cap-e.flow);
                    if(!inq[e.to]) {Q.push(e.to);inq[e.to]=1;}
                }
            }
        }
        if(d[t]==inf) return false;
        flow += a[t];
        cost += (long long)d[t] * (long long)a[t];
        for(int u=t;u!=s;u=edges[p[u]].from) {
            edges[p[u]].flow += a[t];
            edges[p[u]^1].flow -= a[t];
        }
        return true;
    }

    int MincostMaxflow(int s,int t,long long& cost){
        int flow = 0;cost = 0;
        BellmanFord(s,t,flow,cost);
        BellmanFord(s,t,flow,cost);
        return flow;
    }
};

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    MCMF cur;
    cur.init(n);

    for (int i = 1; i <= m; ++i) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c);
        cur.AddEdge(u, v, 1, c);
        cur.AddEdge(v, u, 1, c);
    }

    long long cost;
    cur.MincostMaxflow(1, n, cost);
    cout << cost << endl;
}

HDU 3376 Matrix Again 费用流裸题

拆点+费用流。求最大:把费用变成负数即可。两条路:跑两次bellmanford即可。这题卡内存。。邻接表过不了,Edges结构体不能写成有Edge(int …)😦){}的样子。不然就MLE()
就当是写了个链式前向星的板子吧()。

#include<bits/stdc++.h>
using namespace std;
const int inf = 0x7f7f7f7f;
#define in(x) x
#define out(x) (x+n*n)
// 拆点 + 最大流,卡了邻接表
const int maxn = 720000;
bitset<maxn> inq;
int d[maxn];
int p[maxn];
int a[maxn];
int head[maxn];

struct Edge {
    int from,to,cap,flow,cost,next;
}edges[maxn<<2];

int tot;

struct MCMF {
    int n,m;

    void init(int n){
        this->n = n;
        tot=0;
        memset(head,-1,sizeof(head));
    }

    void AddEdge(int from,int to,int cap,int cost){
        edges[tot++]=(Edge{from,to,cap,0,cost,head[from]});
        edges[tot++]=(Edge{to,from,0,0,-cost,head[to]});
        head[from]=tot-2;
        head[to]=tot-1;
    }

    bool BellmanFord(int s,int t,int& flow,long long& cost) {
        for(int i=0;i<n;++i) d[i]=inf;
        inq.reset();
        d[s] = 0;inq.set(s);p[s] = 0;a[s] = inf;

        queue<int> Q;
        Q.push(s);
        while(!Q.empty()){
            int u=Q.front();Q.pop();
            inq.reset(u);
            for(int i=head[u];~i;i=edges[i].next){
                Edge& e = edges[i];
                if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){
                    d[e.to]=d[u]+e.cost;
                    p[e.to]=i;  // 来的边的序号
                    a[e.to]=min(a[u],e.cap-e.flow);
                    if(!inq[e.to]) {Q.push(e.to);inq.set(e.to);}
                }
            }
        }
        if(d[t]==inf) return false;
        flow += a[t];
        cost += (long long)d[t] * (long long)a[t];
        for(int u=t;u!=s;u=edges[p[u]].from) {
            edges[p[u]].flow += a[t];
            edges[p[u]^1].flow -= a[t];
        }
        return true;
    }

    int MincostMaxflow(int s,int t,long long& cost){
        int flow = 0;cost = 0;
        BellmanFord(s,t,flow,cost);
        BellmanFord(s,t,flow,cost);
        return flow;
    }
};

int main(){
    int n;
    MCMF cur;
    while(~scanf("%d",&n)){
        cur.init(2*n*n);
        int st,ed;
        for(int i=0;i<n;++i){
            for(int j=0;j<n;++j){
                int tmp;
                scanf("%d",&tmp);
                if(i==0&&j==0) st=tmp;
                if(i==n-1&&j==n-1) ed=tmp;
              //  cout<<out(n*(i-1)+j)<<" "<<in(n*i+j)<<endl;
                if(i!=0) cur.AddEdge(out(n*(i-1)+j),in(n*i+j),inf,0);
                if(j!=0) cur.AddEdge(out(n*i+j-1),in(n*i+j),1,0);
                cur.AddEdge(in(n*i+j),out(n*i+j),1,-tmp);
            }
        }
        long long cost;
        int s=out(0),t=in(n*n-1);
        cur.MincostMaxflow(s,t,cost);
        cout<<-cost+st+ed<<endl;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值