网络流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); } }