网络流learning

上次学习网络流还是大一的下学期,之后就被从图论分出来交给队友了

然而吉林一战,队友在深圳读研而不能来,于是需要自己学习一下,争取在比赛前看完网络流建模汇总和一些总结,升华一下。

同时记录一下自己做过的题目和想法,相互映照,得出结论

POJ1149

时间看来是很老的题,不过还没有见过,一开始想出来的办法是给人流量,让圈互相给max边,但是后来发现不对,因为如果先给圈边的话,人的次序就不会对ans造成影响,对于这类有时间限制的题,可以对一些被时间影响到的点进行“持久化”,第i+1个状态的流量由第i个状态的流量转移而来,虽然点数变成了点数*时间,但是在二分图下很快,而n*m是1e5,很明显的暗示。

采用dinic进行初始学习

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<vector>
#include<iostream>
#include<algorithm>
#include<stack>
#include<string>
#include<queue>
using namespace std;
#define pb push_back

#define rep(i, a, b) for (int i=a;i<=b;++i)

const int N = 100 * 1000 + 100 + 100 + 5 ;
const int M = 100 * 1000 * 5 + 5 ;
const int INF = 999999999 ;
int src , des , e , head[N] , dis[N] ;
int n , m ;

struct Edge {
    int v,cap,next;
    Edge(){}
    Edge(int vv,int cc,int ne) {
        v=vv;cap=cc;next=ne;
    }
} ed[M] ;
void init() {
    e = 0 ;
    memset(head,-1,sizeof(head)) ;
}
int bfs() {
    memset(dis,0,sizeof(dis)) ;
    dis[src] = 1 ;
    queue<int>q;
    q.push(src) ;
    while(!q.empty()) {
        int x = q.front(); q.pop() ;
        for(int i = head[x] ; i != -1 ; i = ed[i].next) {
            int v = ed[i].v ;
            if(ed[i].cap && dis[v] == 0) {
                dis[v] = dis[x] + 1 ;
                if(v == des) {
                    return 1 ;
                }
                q.push(v) ;
            }
        }
    }
    return 0 ;
}
int dfs(int s,int f) {
    if(s == des) return f;
    int tmp,cost = 0 ;
    for(int i = head[s] ; i != -1 ; i = ed[i].next) {
        int v = ed[i].v ;
        if(ed[i].cap && dis[s] == dis[v] - 1) {
            tmp = dfs(v,min(f-cost,ed[i].cap)) ;
            if(tmp > 0) {
                ed[i].cap -= tmp;
                ed[i^1].cap += tmp ;
                cost += tmp ;
                if (f == cost)
                    break ;
            }
            else dis[v] = -1 ;
        }
    }
    return cost ;
}
int Dinic() {
    int ans = 0 ;
    while(bfs()) {
        ans += dfs(src,INF) ;
    }
    return ans ;
}

void add(int u,int v,int cap) {
    ed[e] = Edge(v,cap,head[u]) ;
    head[u] = e++ ;
    ed[e] = Edge(u,0,head[v]) ;
    head[v] = e++ ;
}

int a[1050] ;
int b[105][1050] ;
int c[105] ;
int main () {
    while(scanf("%d%d" , &m,&n) != EOF) {
        init();
        src = 0 ;
        des = m * n + n + n + 1 ;
        rep(i,1,m) {
            scanf("%d" , &a[i]) ;
        }

        /// src = 0 ;
        /// pig 1~m m+1~m*2 ... m*n
        /// peo1 m*n + 1~ m*n + n
        /// peo2 m*n + n + 1 ~ m*n + n + n
        /// des m*n+n*2+1
        rep(i,1,n) {
            int nu ; scanf("%d" , &b[i][0]) ;
            rep(j,1,b[i][0]) {
                scanf("%d" , &b[i][j]) ;
            }
            scanf("%d" , &c[i]) ;
        }

        rep(i,1,m) {
            add(src, i, a[i]) ;
        }

        rep(i,1,n-1) {
            rep(j,1,m) {
                add((i-1)*m+j,(i)*m+j,INF) ;
            }
            rep(j,1,b[i][0]) {
                rep(k,1,b[i][0]) {
                    // if(j == k) continue ;
                    int u = (i-1)*m + b[i][j];
                    int v = (i)*m + b[i][k] ;
                    add(u,v, INF) ;
                }
            }
        }

        rep(i,1,n) {
            int u = m * n + i ;
            int v = m * n + n + i ;
            add(u,v, c[i]) ;
        }

        rep(i,1,n) {
            int v = m * n + i ;
            rep(j,1,b[i][0]) {
                int u = (i - 1) * m + b[i][j] ;
                add(u,v, INF) ;
            }
        }

        rep(i,1,n) {
            int u = m * n + n + i ;
            int v = des ;
            add(u,v, INF) ;
        }

        printf("%d\n" , Dinic()) ;
    }
}

 感想:千万不能写错板子,dinic其实是这样的,bfs确定可流,dfs来找出最大的那条流量边,多次dfs其实是M*N*M的复杂度 dfs数量*增广时间

 poj2391

这个比较老,一眼就能看出是floyd取minDis然后建二分图,不拆点肯定是错的,甚至连时间都限制不了?

一个坑在于,每次二分都需要重新加边,因为每次图的状态都是残余流量,当然因为i+i^1的cap其实就是i,所以也可以直接扫边更新,不过扫边更新后需要更改板子,需要判断的比较多

 

后来在比赛的前一天晚上看完了网络流建模汇总,比赛不出意料的没有出网络流。。

工作做完了在学校应该会补完这一部分,为了青岛而努力一下,LCT似乎也是很有趣的算法,不过也需要看一下书。

                                                                                                    to be continue....

 

因为备战EC,自己需要负责网络流,所以重新捡起,主要学习最小费用流

POJ3680 

很经典的题型,200个区间中选一些,获得她们的权值,这些区间不能将任意一个点覆盖K次以上

思考发现,如果仅仅从区间的角度上来考虑,将区间化点,是无法在MCMF的过程中控制点被覆盖的次数的,总不能给每个点记录当前被覆盖多少次然后check是否连通。。

所以从点的角度思考,假设有一条从最左点依次向右的流(i,i+1),流量为k,那么每个区间其实是将l到r的流量都减一,然后直接从l跨越到r,因为到l的流量也是从0点的K开始不断的衰减,所以直接进行下去就可以,即建一条边(l,r,1,-c),最后跑0~m的MCMF

int main () {
    int TT ; scanf("%d" , &TT) ;
    while(TT--){
        int n , k ;
        scanf("%d%d" , &n,&k) ;
        vector<int>ls;
        rep(i,1,n) {
            scanf("%d%d%d" , &l[i],&r[i],&c[i]) ;
            ls.pb(l[i]) ;
            ls.pb(r[i]) ;
        }
        sort(ls.begin(),ls.end());
        ls.erase(unique(ls.begin(),ls.end()),ls.end()) ;
        rep(i,1,n) {
            l[i] = lower_bound(ls.begin(),ls.end(),l[i])-ls.begin()+1;
            r[i] = lower_bound(ls.begin(),ls.end(),r[i])-ls.begin()+1;
        }
        init() ;
        int m = ls.size() ;
        for(int i = 0 ; i <= m ; i ++ ) {
            addEdge(i,i+1,k,0) ;
        }
        for(int i = 1 ; i <= n ; i ++ ) {
            addEdge(l[i],r[i],1,-c[i]) ;
        }
        int cost,flow;
        MCMF(0,m+1,cost,flow);
        printf("%d\n" , -cost) ;
    }
}

  

SPOJ371 

n(1000)个箱子里有总共n个小球,每次可以移动一个球到相邻箱子中,问多少步可以让每个箱子最多一个球。

建图比较好想,把球当作流量,des可以从每个箱子中回收一个流量,src会给每个箱子a[i]的流量,流量在箱子之间穿梭的cost是1

建图 (src,i,a[i],0) (i,i-1,INF,1)(i,i+1,INF,1)(i,des,1,0)

不过复杂度是玄学的,spfa是nmm的,t还有20组,看起来有1e10,但是由于spfa复杂度很玄,还是过了

int main () {
    int TT ; scanf("%d" , &TT) ;
    while(TT--){
        int n ; scanf("%d" , &n) ;
        rep(i,1,n) scanf("%d" , &a[i]) ;
        init() ;
        rep(i,1,n) {
            addEdge(0,i,a[i],0) ;
            addEdge(i,n+1,1,0) ;
            int le=i-1;
            if(le==0)le=n;
            addEdge(i,le,INF,1);
            le=i+1;
            if(le==n+1)le=1;
            addEdge(i,le,INF,1);
        }
        int cost,flow;
        MCMF(0,n+1,cost,flow);
        printf("%d\n",cost);
    }
}

  

posted @ 2018-09-20 15:54  天翎月  阅读(271)  评论(1编辑  收藏  举报