hdu 4289 control
http://acm.hdu.edu.cn/showproblem.php?pid=4289
我自己的代码:
2 #include<cstring>
3 #include<cmath>
4 #include<iostream>
5 #include<algorithm>
6 #include<set>
7 #include<map>
8 #include<queue>
9 #include<vector>
10 #include<string>
11 #define INF 0x7fffffff
12 #define maxn 500
13 #define CL(a,b) memset(a,b,sizeof(a))
14
15 using namespace std;
16 struct node
17 {
18 int to;
19 int cap;
20 int next;
21 }p[200000] ;
22 int dis[maxn],gap[maxn] ,cnt,next[maxn],s,e;// dis[i]为 到达 原点的层数
23 int n , m,NN ;//NN 为 加完点 之后的 总结点数
24 void add(int from,int to,int cap)//加 的 是 有向边
25 {
26 p[cnt].to = to;
27 p[cnt].cap = cap ;
28 p[cnt].next = next[from];
29 next[from] = cnt++;
30
31 p[cnt].to = from;
32 p[cnt].cap = 0;
33 p[cnt].next = next[to];
34 next[to] = cnt++ ;
35 }
36 int dfs(int pos,int cost)
37 {
38
39 if(pos == e)
40 return cost ;
41
42 int i,j ,mdis = NN ,f = cost ;
43
44 for(i = next[pos];i != - 1; i = p[i].next)
45 {
46 int to = p[i].to ;
47 int cap = p[i].cap ;
48 if(cap > 0 )
49 {
50 if(dis[to] + 1 == dis[pos])
51 {
52
53
54 int d = min(f,cap) ;// 注意 这 为 剩余 流量 和 cap 的 最小值
55
56 d = dfs(to,d) ;
57 p[i].cap -=d;
58 p[i^1].cap +=d;
59 f -= d;
60
61 if(dis[s] >= NN) return cost - f;// 如果没有 了 增广路经 结束算法
62 if(f == 0) break ;
63 }
64 if( dis[to] < mdis ) mdis = dis[to] ;// 记录可扩展的最小的狐
65
66 }
67
68 }
69 if(f == cost)// 没有 可以 扩展的点
70 {
71 --gap[dis[pos]];
72 if(gap[dis[pos]] == 0)dis[s] = NN;// 注意这 ,若 距离 为 dis[pos] 这一层都没有 扩展点了(断层) dis[s] = n
73
74 dis[pos] = mdis + 1;//维护距离标号的方法是这样的:当找增广路过程中发现某点出发没有允许弧时,将这个点的距离标号设为由它出发的所有弧的终点的距离标号的最 小值加一
75
76 ++gap[dis[pos]] ;
77 }
78 return cost - f ;
79 }
80 int isap( int b,int t)
81 {
82
83 int ret = 0;
84 s = b;
85 e = t;
86 CL(gap,0);
87 CL(dis,0) ;
88 gap[s] = NN ;//NN 为 加完点 之后的 总结点数
89 while(dis[s] < NN)
90 {
91 ret+=dfs(s,INF) ;
92 }
93 return ret ;
94
95 }
96 int main()
97 {
98 int i,x,y,a,b,t;
99 //freopen("data.txt","r",stdin) ;
100 while(scanf("%d%d%d%d",&n,&m,&b,&t)!=EOF)
101 {
102 CL(next,-1);
103 cnt = 0 ;
104 NN = 2*n ;//NN 为 加完点 之后的 总结点数
105 for(i = 1;i <=n;i++)
106 {
107 scanf("%d",&a);
108 add(i,i+n,a) ;
109 }
110 for(i = 0; i<m;i++)
111 {
112 scanf("%d%d",&x,&y);
113 add(x+n,y,INF);
114 add(y + n,x,INF);
115 }
116
117 int ans = isap(b,t + n) ;
118 printf("%d\n",ans) ;
119 }
120 }
讲解:
http://hi.baidu.com/jhubtjkpmbfpqzr/item/9223a400c14418dbdce5b027
另一 讲解:
原来我的模板是这么来的。至今思网络,不知怎么流。惭愧啊……
ISAP全称Improved Shortest Augmenting Path,由Ahuja和Orlin在1987年提出,而下文讲的是加上gap优化的ISAP。
顺便,其实这篇写得比较入门和清楚。
====
这几天由于种种原因经常接触到网络流的题目,这一类型的题给人的感觉,就是要非常使劲的YY才能出来点比较正常的模型。尤其是看了Amber最 小割应用的文章,里面的题目思路真是充满了绵绵不绝的YD思想。然而比赛中,当你YD到了这一层后,您不得不花比较多的时间去纠结于大量细节的实现,而冗 长的代码难免会使敲错版后的调试显得异常悲伤,因此一些巧妙简短高效的网络流算法在此时便显得犹为重要了。本文力求以最简短的描述,对比较流行的网络流算 法作一定的总结,并借之向读者强烈推荐一种效率与编程复杂度相适应的算法。
众所周知,在网络流的世界里,存在2类截然不同的求解思想,就是比较著名的预流推进与增广路,两者都需要反向边的小技巧。
其中预流推进的算法思想是以边为单元进行推流操作。具体流程如下:置初始点邻接边满流并用一次反向bfs对每个结点计算反向距离标号,定义除汇 点外存量大于出量的结点为活动结点,每次对活动结点按允许边(u->v:d[u]=d[v]+1)进行推流操作,直到无法推流或者该点存量为0,若 u点此时仍为活动结点,则进行重标号,使之等于原图中进行推操作后的邻接结点的最小标号+1,并将u点入队。当队列为空时,算法结束,只有s点和t点存量 非0,网络中各顶点无存量,无法找到增广路继续增广,则t点存量为最大流。
而增广路的思想在于每次从源点搜索出一条前往汇点的增广路,并改变路上的边权,直到无法再进行增广,此时汇点的增广量即为最大流。两者最后的理 论基础依然是增广路定理,而在理论复杂度上预流推进要显得比较优秀。其中的HLPP高标预流推进的理论复杂度已经达到了另人发指的 O(sqrt(m)*n*n),但是其编程复杂度也是同样的令人发指- -
于是我们能否在编程复杂度和算法复杂度上找到一个平衡呢,答案是肯定的。我们使用增广路的思想,而且必须进行优化。因为原始的增广路算法(例如 EK)是非常悲剧的。于是有人注意到了预流推进中的标号法,在增广路算法中引入允许弧概念,每次反搜残留网络得到结点标号,在正向增广中利用递归进行连续 增广,于是产生了基于分层图的Dinic算法。一些人更不满足于常规Dinic所带来的提升,进而加入了多路分流增广的概念,即对同一顶点的流量,分多路 同时推进,再加上比较复杂的手工递归,使得Dinic已经满足大部分题目的需要。
然而这样做就是增广路算法优化的极限么?答案永远是不。人们在Dinic中只类比了预流推进的标号技术,而重标号操作却没有发挥得淋漓尽致。于 是人们在Dinic的基础上重新引入了重标号的概念,使得算法无须在每次增广后再进行BFS每个顶点进行距离标号,这种主动标号技术使得修正后算法的速度 有了不少提高。但这点提高是不足称道的,人们又发现当某个标号的值没有对应的顶点后,即增广路被截断了,于是算法便可以提前结束,这种启发式的优化称为 Gap优化。最后人们结合了连续增广,分层图,多路增广,Gap优化,主动标号等穷凶极恶的优化,更甚者在此之上狂搞个手动递归,于是产生了增广路算法的 高效算法–ISAP算法。
虽然ISAP算法的理论复杂度仍然不可超越高标预流推进,但其编程复杂度已经简化到发指,如此优化,加上不逊于Dinic的速率(在效率上手工Dinic有时甚至不如递归ISAP),我们没有不选择它的理由。
因此本文强烈推荐ISAP作为网络流首选算法。
其实现方法见下文,除去例行的添边操作,不超过50行的代码,何乐而不为之,以下实现仍有优化的余地(在计算初始标号时,为减小代码量直接忽略之,其复杂度不变,但实现后效率有5%左右的下降,如果乐意修正的话可以进行改良,当然不修正不影响算法正确性)。
2 const int MAXN=20010;
3 const int MAXM=500010;
4
5 edge e[MAXM];
6 int p[MAXN],eid;
7
8 inline void init(){memset(p,-1,sizeof(p));eid=0;}
9
10 //有向
11 inline void insert1(int from,int to,int val)
12 {
13 e[eid].v=to;
14 e[eid].val=val;
15 e[eid].next=p[from];
16 p[from]=eid++;
17
18 swap(from,to);
19
20 e[eid].v=to;
21 e[eid].val=0;
22 e[eid].next=p[from];
23 p[from]=eid++;
24 }
25
26 //无向
27 inline void insert2(int from,int to,int val)
28 {
29 e[eid].v=to;
30 e[eid].val=val;
31 e[eid].next=p[from];
32 p[from]=eid++;
33
34 swap(from,to);
35
36 e[eid].v=to;
37 e[eid].val=val;
38 e[eid].next=p[from];
39 p[from]=eid++;
40 }
41
42 int n,m;//n为点数 m为边数
43 int h[MAXN];
44 int gap[MAXN];
45
46 int source,sink;
47 inline int dfs(int pos,int cost)
48 {
49 if (pos==sink)
50 {
51 return cost;
52 }
53
54 int j,minh=n-1,lv=cost,d;
55
56 for (j=p[pos];j!=-1;j=e[j].next)
57 {
58 int v=e[j].v,val=e[j].val;
59 if(val>0)
60 {
61 if (h[v]+1==h[pos])
62 {
63 if (lv<e[j].val) d=lv;
64 else d=e[j].val;
65
66 d=dfs(v,d);
67 e[j].val-=d;
68 e[j^1].val+=d;
69 lv-=d;
70 if (h[source]>=n) return cost-lv;
71 if (lv==0) break;
72 }
73
74 if (h[v]<minh) minh=h[v];
75 }
76 }
77
78 if (lv==cost)
79 {
80 --gap[h[pos]];
81 if (gap[h[pos]]==0) h[source]=n;
82 h[pos]=minh+1;
83 ++gap[h[pos]];
84 }
85
86 return cost-lv;
87
88 }
89
90 int sap(int st,int ed)
91 {
92
93 source=st;
94 sink=ed;
95 int ret=0;
96 memset(gap,0,sizeof(gap));
97 memset(h,0,sizeof(h));
98
99 gap[st]=n;
100
101 while (h[st]<n)
102 {
103 ret+=dfs(st,INT_MAX);
104 }
105
106 return ret;
107 }