ACM图论行云流水.docx

上传人:b****7 文档编号:9480374 上传时间:2023-02-04 格式:DOCX 页数:194 大小:369.13KB
下载 相关 举报
ACM图论行云流水.docx_第1页
第1页 / 共194页
ACM图论行云流水.docx_第2页
第2页 / 共194页
ACM图论行云流水.docx_第3页
第3页 / 共194页
ACM图论行云流水.docx_第4页
第4页 / 共194页
ACM图论行云流水.docx_第5页
第5页 / 共194页
点击查看更多>>
下载资源
资源描述

ACM图论行云流水.docx

《ACM图论行云流水.docx》由会员分享,可在线阅读,更多相关《ACM图论行云流水.docx(194页珍藏版)》请在冰豆网上搜索。

ACM图论行云流水.docx

ACM图论行云流水

最大流3

用最大流解路径问题7

用最大流解竞赛得分问题15

用最大流解分配资源问题18

用最大流解流水线问题22

用最大流解二分图多重匹配23

字典序最小的最大流29

二分答案+最大流判定32

动态最大流(分层)36

巧用拆点解点限制的最大流41

最小割46

最小点权割集46

最小割的关键割边集49

字典序最小的割边集51

用最小割解最大权闭合图问题54

用最小割解二分图最小点权覆盖问题55

用最小割解二分图最大点权独立集问题56

用最小割解图的连通度问题59

用最小割解收入与支出问题63

用最小割解二者选其一问题67

上下界流76

无源汇上下界可行流78

有源汇上下界可行流79

有源汇上下界最大流82

有源汇上下界最小流84

费用流85

费用流普通构图方法91

区间覆盖问题构图方法93

资源分配问题构图方法94

度限制问题的构图方法97

分层构图求最小费用99

源汇之间k条路径的权值和最小100

消圈算法求更小费用103

费用有正负的最小费用可行流105

拆边解凸费用流107

平面图网络流转其他算法111

平面图最小割转最短路径111

平面图最大流转贪心115

平面图下界最小流转偏序集117

最大流

最大流应用比较广泛的主要有dinic和sap_gap算法:

Dinic算法:

步骤为:

1)bfs()计算残余网络的层次图。

我们定义h[i]为顶点i距离源S所经过到最小边数,求出所有顶点的h值,h[]值相同的顶点属于同一层,这就是网络的层次图,直到不存在h[T]==-1即不存在从S到T的增广路径。

2)在层次图上进行dfs修改残余网络。

我们可以在层次图上进行多次增广。

3)重复1,2的过程,直到找不到增广路为止。

#include

#include

#defineINF999999999

#definemin(x,y)(x)>(y)?

(y):

(x)

structp

{

intv,t,k,next;

}edge[51000];

intn,m,k,S,T,head[510],tot,h[510];

voidaddedge(inta,intb,intk)

{

edge[tot].v=b;

edge[tot].k=k;

edge[tot].t=tot+1;

edge[tot].next=head[a];

head[a]=tot++;

edge[tot].v=a;

edge[tot].k=0;

edge[tot].t=tot-1;

edge[tot].next=head[b];

head[b]=tot++;

}

intbfs()

{

inti,top,tail,cur,pop[510];

top=tail=0;

memset(h,0xff,sizeof(h));

h[S]=0;

pop[top++]=S;

while(tail!

=top)

{

cur=pop[tail++];

for(i=head[cur];i!

=-1;i=edge[i].next)

if(edge[i].k>0&&h[edge[i].v]==-1)

{

h[edge[i].v]=h[cur]+1;

pop[top++]=edge[i].v;

}

}

returnh[T]!

=-1;

}

intdfs(intt,intflow)

{

inti,tmpf,f;

if(t==T)returnflow;

tmpf=0;

for(i=head[t];i!

=-1;i=edge[i].next)

if(edge[i].k>0&&h[edge[i].v]==h[t]+1&&

tmpf

{

edge[i].k-=f;

edge[edge[i].t].k+=f;

tmpf+=f;

}

if(tmpf==0)h[t]=-1;

returntmpf;

}

intdinic()

{

intmaxflow=0;

while(bfs())

maxflow+=dfs(S,INF);

returnmaxflow;

}

 

Sap+gap算法:

Dinic算法要多次计算层次图,增加了复杂度。

是不是可以不多次计算层次图呢?

答案是肯定,这就产生了SAP算法。

SAP计算的是反向图的层次图,这和原图的层次图是作用是一样的,当然其实Dinic也可以计算反向图的层次图。

计算反向图的层次图是便于重新给顶点标号,即重新确定其层次图。

具体做法为,当我们找到一条经过顶点i的增广路径后,对于所有边,计算出m=min{h[j]},这是我们就可以把i重新标号为h[i]=min+1。

实际上,我们可以首先不需要计算反向图的层次图,而是把所有顶点的层次标为0,这对效率没多大影响。

优化1):

gap优化(cnt[])。

所谓gap优化就是计算出层次图后,层次出现断层,这是可以确定残余网络中不存在增广路径了,算法就可以提前结束。

这个优化看似微小,实际作用确不小。

做法就是保存某一个标号在残余网络中出现的次数,如果是0,就断层了。

优化2):

当前弧优化(cur[]、cur1[])。

为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”:

初始时当前弧是邻接表的第一条弧;在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;改变距离标号时,把当前弧重新设为邻接表的第一条弧。

#include

#include

#include

#defineINF99999999

#definemin(x,y)(x)>(y)?

(y):

(x)

usingnamespacestd;

structp

{

intv,t,k,next;

}edge[51000];

inttot,n,m,S,T,h[410],head[510],cnt[510];

voidaddedge(inta,intb,intk)

{

edge[tot].v=b;

edge[tot].t=tot+1;

edge[tot].k=k;

edge[tot].next=head[a];

head[a]=tot++;

edge[tot].v=a;

edge[tot].t=tot-1;

edge[tot].k=0;

edge[tot].next=head[b];

head[b]=tot++;

}

intdfs(intt,intflow)

{

inti,tmpf,f,tmp;

tmpf=flow;

tmp=T;

if(t==T)returnflow;

for(i=head[t];i!

=-1;i=edge[i].next)

{

if(h[t]==h[edge[i].v]+1&&edge[i].k>0)

{

f=dfs(edge[i].v,min(edge[i].k,tmpf));

tmpf-=f;

edge[i].k-=f;

edge[edge[i].t].k+=f;

if(!

tmpf||h[S]==T+1)returnflow-tmpf;

}

if(h[edge[i].v]0)tmp=h[edge[i].v];

}

if(!

(--cnt[h[t]]))h[S]=T+1;

elsecnt[h[t]=tmp+1]++;

returnflow-tmpf;

}

intsap_gap()

{

intmaxflow;

maxflow=0;

memset(h,0,sizeof(h));

memset(cnt,0,sizeof(cnt));

while(h[S]

maxflow+=dfs(S,INF);

returnmaxflow;

}

用最大流解路径问题

Poj2455Secret Milking Machinetime497

题意:

求从S到T的t条路径,且路径上的边都不同,且最大的边最小。

题解:

这类题,要的不是整体的最小费用,一般都是二分答案(最长所允许的边的费用),筛掉二分时mid之上费用的边,然后用最大流算看它是不是满流的来决定继续怎么分。

至于那个正反边都建的问题,其实它是不可能正反都走的,因为我们的目的是要让路径走的最长的那条尽可能短。

如果都走的话,看下面的图

A

/\

/\

B----C

\/

\/

D

假设我们先走A->B->C->D,再走A->C->B->D。

那么实际上,我可以直接走A->B->D和A->C->D。

B->C这一段是完全剩下的。

也就是说,对于任何一条被走正反两次的路,你正走的那个起点和反走的那个终点其实是同一个点,先前走的那条路的前一半A->B,必然和之后走的路的后一半B->D相连,对于另一边也是这样。

完全可以不走那段被重复走的。

而且如果走了中间那段,反而是多走的。

至于用单源最短路先定下边的方向,无法保证正确,仔细想想就很容易举出反例了。

代码:

intmain()

{

intk,i,j,a,b,l,t,r,mid,ans;

while(scanf("%d%d%d",&n,&m,&k)!

=EOF)

{

l=INF,r=-1;

for(i=0;i

{

scanf("%d%d%d",&a,&b,&t);

if(t>r)r=t;

if(t

line[i].a=a;

line[i].b=b;

line[i].k=t;

}

l--,r++;

S=n+1,T=n+2;

while(l+1!

=r)

{

mid=(l+r)/2;

tot=0;

memset(head,0xff,sizeof(head));

for(i=0;i

if(line[i].k<=mid)

{

addedge(line[i].a,line[i].b,1);

addedge(line[i].b,line[i].a,1);

}

addedge(S,1,k);

addedge(n,T,k);

if(dinic()==k)ans=mid,r=mid;

elsel=mid;

}

printf("%d\n",ans);

}

return0;

}

最长递增子序列问题

题意:

给定正整数序列x1…xn.。

1.计算其最长递增子序列的长度s。

2.计算从给定的序列中最多可取出多少个长度为s的递增子序列。

3.如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的递增子序列。

题解:

首先动态规划求出dp[i],表示以第i位为结尾的最长上升序列的长度,求出最长上升序列长度K。

然后把序列每位i拆成两个点,从连接一条容量为1的有向边。

建立附加源S和汇T,如果序列第i位有dp[i]=K,从到T连接一条容量为1的有向边。

如果dp[i]=0,从S到连接一条容量为1的有向边。

如果j>i且d[i]连接一条容量为1的有向边。

求网络最大流,就是第二问的结果。

把边(<1.a>,<1.b>)(,)(S,<1.a>)(,T)这四条边的容量修改为无穷大,再求一次网络最大流,就是第三问结果。

intmain()

{

inti,j,k,max,d[1000],dp[1000];

while(scanf("%d",&n)!

=EOF)

{

if(n==0)continue;

for(i=1;i<=n;i++)

scanf("%d",&d[i]);

memset(dp,0,sizeof(dp));

for(i=1;i<=n;i++)

{

max=0;

for(j=1;j

if(d[j]max)

max=dp[j]+1;

dp[i]=max;

}

max=0;

for(i=1;i<=n;i++)

if(max

max=dp[i];

printf("%d\n",max+1);

memset(head,0xff,sizeof(head));

tot=0;

S=0;T=2*n+1;

for(i=1;i<=n;i++)

{

addedge(i,i+n,1);

if(dp[i]==0)addedge(S,i,1);

if(dp[i]==max)addedge(i+n,T,1);

for(j=i+1;j<=n;j++)

if(dp[j]==dp[i]+1&&d[j]>d[i])

addedge(i+n,j,1);

}

printf("%d\n",dinic());

memset(head,0xff,sizeof(head));

tot=0;

S=0;T=2*n+1;

for(i=1;i<=n;i++)

{

if(i==1||i==n)addedge(i,i+n,INF);

elseaddedge(i,i+n,1);

if(dp[i]==0&&i==1)addedge(S,i,INF);

elseif(dp[i]==0)addedge(S,i,1);

if(dp[i]==max&&i==n)addedge(i+n,T,INF);

elseif(dp[i]==max)addedge(i+n,T,1);

for(j=i+1;j<=n;j++)

if(dp[j]==dp[i]+1&&d[j]>d[i])

addedge(i+n,j,1);

}

printf("%d\n",dinic());

}

return0;

}

Zoj2760HowManyShortestPathtime80ms

题目大意:

求一个有向图中点到点的边不相交的最短路径条数。

算法:

floyd+最大流

思路:

先求出最短路径的大小,然后再每条边依次判断是否在最短路径上,如果在最短路径上则添加一条流量大小为1的边。

最后再求最大流,最大流值就是路径的条数。

判断边是否在最短路径上:

(其中map[i][j]是点i到点j的最短距离,dt[i][j]是边i到j的大小)

for(i=0;i

for(j=0;j

if(i!

=j&&dt[i][j]!

=-1&&map[S][i]!

=-1&&map[j][T]!

=-1&&

map[S][T]==map[S][i]+dt[i][j]+map[j][T])

addedge(i,j,1);//说明在最短路径上,则添边

intmain()

{

inti,j,k,map[110][110],dt[110][110];

while(scanf("%d",&n)!

=EOF)

{

for(i=0;i

for(j=0;j

{

scanf("%d",&map[i][j]);

dt[i][j]=map[i][j];

}

for(i=0;i

dt[i][i]=map[i][i]=0;

scanf("%d%d",&S,&T);

if(S==T)

{

printf("inf\n");

continue;

}

for(k=0;k

for(i=0;i

if(k!

=i)for(j=0;j

if(j!

=i&&j!

=k)

{

if(map[i][k]!

=-1&&map[k][j]!

=-1&&

(map[i][j]==-1||map[i][j]>map[i][k]+map[k][j]))

map[i][j]=map[i][k]+map[k][j];

}

if(map[S][T]==-1)

{

printf("0\n");

continue;

}

tot=0;

memset(head,0xff,sizeof(head));

for(i=0;i

for(j=0;j

if(i!

=j&&dt[i][j]!

=-1&&map[S][i]!

=-1&&map[j][T]!

=-1&&

map[S][T]==map[S][i]+dt[i][j]+map[j][T])

addedge(i,j,1);

printf("%d\n",dinic());

}

return0;

}

Sgu185Twoshortest

题意:

求1->n的两条不相交的最短路(两条路径可以共顶点但是不能共边)

做法:

用spfa求出点1到各个点的最短路径,然后再if(dis[j]==dis[i]+map[i][j])来判断边map[i][j]是否是在最短路径上,如果是就加到图里,结果就得到以1为顶点的最短路径树,1到树中任意一点的连线都是最短路径,首先把这些边加到网络流的边集中,容量为1。

跑一遍1到n的最大流,如果流量>=2则有解,再从原点深搜路径即可。

确切的来说,只要在后来建的图中随便找一条路径均是原点到该点的最短路(注意边是单向的),又因为限制了流量是1,所以一条边只能选取一次,这样跑出来的流量就一定是不相交最短路的条数。

当然如果在后面建的最短路径树中直接求两条从1到n的路径是不行的,如下图:

如果边的长度都为1,当找的第一条路径是1->4->3->6时,然后就找不到其他最短路径了,也即只能找到一条,但是图中明显有两条最短路径1->2->3->6和1->4->5->6所以直接求两条从1到n的路径是不行的,因此只能用最大流来做。

代码如下:

intspfa()

{

inti,j,cur,tmp,mark[410],dis[410];

queueque;

memset(mark,0,sizeof(mark));

for(i=0;i<=n;i++)

dis[i]=INF;

dis[1]=0;

mark[1]=1;

que.push

(1);

while(!

que.empty())

{

cur=que.front();

que.pop();

mark[cur]=0;

for(i=head[cur];i!

=-1;i=edge[i].next)

if(dis[edge[i].v]>dis[cur]+edge[i].k)

{

dis[edge[i].v]=dis[cur]+edge[i].k;

if(mark[edge[i].v]==0)

{

mark[edge[i].v]=1;

que.push(edge[i].v);

}

}

}

if(dis[n]==INF)return0;

tmp=tot;

tot=0;

memset(head,0xff,sizeof(head));

S=0,T=n+1;

for(i=1;i<=n;i++)

for(j=i+1;j<=n;j++)

if(i!

=j&&map[i][j]!

=-1)

{

if(dis[i]==dis[j]+map[j][i])

addedge(j,i,1);

if(dis[j]==dis[i]+map[i][j])

addedge(i,j,1);

}

addedge(S,1,2);

addedge(n,T,2);

return1;

}

voiddfs2(intt)

{

inti;

printf("%d",t);

if(t==n)return;

for(i=head[t];i!

=-1;i=edge[i].next)

if(i%2==0&&edge[i].k==0)

{

dfs2(edge[i].v);

edge[i].k=1;

return;

}

}

intmain()

{

inti,j,a,b,c;

scanf("%d%d",&n,&m);

tot=0;

memset(head,0xff,sizeof(head));

memset(map,0xff,sizeof(map));

for(i=0;i

{

scanf("%d%d%d",&a,&b,&c);

if(map[a][b]==-1||map[a][b]>c)

map[a][b]=map[b][a]=c;

}

for(i=1;i<=n;i++)

for(j=i+1;j<=n;j++)

if(map[i][j]!

=-1)

addedge1(i,j,map[i][j]);

if(spfa()==0||sap_gap()<2)

printf("Nosolution\n");

else

{

dfs2

(1);

printf("\n");

dfs2

(1);

printf("\n");

}

return0;

}

SPOJ962IntergalacticMap

题目大意:

在一个无向图中,一个人要从A点赶往B点,之后再赶往C点,且要求中途不能多次经过同一个点。

问是否存在这样的路线。

(3<=N<=30011,1<=M<=50011)

建模方法:

由于每个点只能走一次,似乎最短路之类的算法不能用,只有往网络流上靠。

将每个点i拆成两个点i’,i’’并加边(i’,i’’,1)就能轻易达到这个目的。

然后我们以B为源点,A、C为汇点,看能否增广两次。

intmain()

{

inti,ca,a,b;

freopen("1.txt","r",stdin);

scanf("%d",&ca);

while(ca--)

{

scanf("%d%d",&n,&m);

tot=0;

memset(head,0xff,sizeof(head));

for(i=0;i

{

scanf("%d%d",&a,&b);

addedge(a,b,1);

addedge(b,a,1);

}

S=n+1;

T=n+2;

addedge(S,2,2);

addedge(1,T,1);

addedge(3,T,1);

if(dinic()==2)printf("YES\n");

elseprintf("NO\n");

}

return

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 文学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1