ACM图论行云流水Word文件下载.docx
《ACM图论行云流水Word文件下载.docx》由会员分享,可在线阅读,更多相关《ACM图论行云流水Word文件下载.docx(194页珍藏版)》请在冰豆网上搜索。
2)在层次图上进行dfs修改残余网络。
我们可以在层次图上进行多次增广。
3)重复1,2的过程,直到找不到增广路为止。
#include<
stdio.h>
string.h>
#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]!
intdfs(intt,intflow)
inti,tmpf,f;
if(t==T)returnflow;
tmpf=0;
for(i=head[t];
h[edge[i].v]==h[t]+1&
tmpf<
flow&
(f=dfs(edge[i].v,min(edge[i].k,flow-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的增广路径后,对于所有边<
i,j>
,计算出m=min{h[j]},这是我们就可以把i重新标号为h[i]=min+1。
实际上,我们可以首先不需要计算反向图的层次图,而是把所有顶点的层次标为0,这对效率没多大影响。
优化1):
gap优化(cnt[])。
所谓gap优化就是计算出层次图后,层次出现断层,这是可以确定残余网络中不存在增广路径了,算法就可以提前结束。
这个优化看似微小,实际作用确不小。
做法就是保存某一个标号在残余网络中出现的次数,如果是0,就断层了。
优化2):
当前弧优化(cur[]、cur1[])。
为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”:
初始时当前弧是邻接表的第一条弧;
在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;
改变距离标号时,把当前弧重新设为邻接表的第一条弧。
queue>
#defineINF99999999
usingnamespacestd;
inttot,n,m,S,T,h[410],head[510],cnt[510];
inti,tmpf,f,tmp;
tmpf=flow;
tmp=T;
if(h[t]==h[edge[i].v]+1&
edge[i].k>
0)
f=dfs(edge[i].v,min(edge[i].k,tmpf));
tmpf-=f;
if(!
tmpf||h[S]==T+1)returnflow-tmpf;
if(h[edge[i].v]<
tmp&
0)tmp=h[edge[i].v];
(--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]<
T+1)
用最大流解路径问题
Poj2455Secret
Milking
Machinetime497
题意:
求从S到T的t条路径,且路径上的边都不同,且最大的边最小。
题解:
这类题,要的不是整体的最小费用,一般都是二分答案(最长所允许的边的费用),筛掉二分时mid之上费用的边,然后用最大流算看它是不是满流的来决定继续怎么分。
至于那个正反边都建的问题,其实它是不可能正反都走的,因为我们的目的是要让路径走的最长的那条尽可能短。
如果都走的话,看下面的图
A
/\
/\
B----C
\/
D
假设我们先走A->
B->
C->
D,再走A->
D。
那么实际上,我可以直接走A->
D和A->
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<
m;
i++)
scanf("
a,&
b,&
t);
if(t>
r)r=t;
if(t<
l)l=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));
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拆成两个点<
i.a>
和<
i.b>
,从<
到<
连接一条容量为1的有向边。
建立附加源S和汇T,如果序列第i位有dp[i]=K,从<
到T连接一条容量为1的有向边。
如果dp[i]=0,从S到<
如果j>
i且d[i]<
d[j]且dp[j]=dp[i]+1,从<
j.a>
求网络最大流,就是第二问的结果。
把边(<
1.a>
<
1.b>
)(<
N.a>
N.b>
)(S,<
T)这四条边的容量修改为无穷大,再求一次网络最大流,就是第三问结果。
inti,j,k,max,d[1000],dp[1000];
%d"
n)!
if(n==0)continue;
for(i=1;
=n;
d[i]);
memset(dp,0,sizeof(dp));
max=0;
for(j=1;
j<
i;
j++)
if(d[j]<
d[i]&
dp[j]+1>
max)
max=dp[j]+1;
dp[i]=max;
if(max<
dp[i])
max=dp[i];
max+1);
S=0;
T=2*n+1;
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;
if(dp[j]==dp[i]+1&
d[j]>
d[i])
addedge(i+n,j,1);
dinic());
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);
Zoj2760HowManyShortestPathtime80ms
题目大意:
求一个有向图中点到点的边不相交的最短路径条数。
算法:
floyd+最大流
思路:
先求出最短路径的大小,然后再每条边依次判断是否在最短路径上,如果在最短路径上则添加一条流量大小为1的边。
最后再求最大流,最大流值就是路径的条数。
判断边是否在最短路径上:
(其中map[i][j]是点i到点j的最短距离,dt[i][j]是边i到j的大小)
n;
for(j=0;
if(i!
=j&
dt[i][j]!
=-1&
map[S][i]!
map[j][T]!
map[S][T]==map[S][i]+dt[i][j]+map[j][T])
addedge(i,j,1);
//说明在最短路径上,则添边
inti,j,k,map[110][110],dt[110][110];
map[i][j]);
dt[i][j]=map[i][j];
dt[i][i]=map[i][i]=0;
%d%d"
S,&
T);
if(S==T)
inf\n"
);
continue;
for(k=0;
k<
k++)
if(k!
=i)for(j=0;
if(j!
=i&
j!
=k)
if(map[i][k]!
map[k][j]!
(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)
0\n"
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->
6和1->
5->
6所以直接求两条从1到n的路径是不行的,因此只能用最大流来做。
代码如下:
intspfa()
inti,j,cur,tmp,mark[410],dis[410];
queue<
int>
que;
memset(mark,0,sizeof(mark));
dis[i]=INF;
dis[1]=0;
mark[1]=1;
que.push
(1);
while(!
que.empty())
cur=que.front();
que.pop();
mark[cur]=0;
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;
S=0,T=n+1;
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(S,1,2);
addedge(n,T,2);
return1;
voiddfs2(intt)
inti;
%d"
t);
if(t==n)return;
if(i%2==0&
edge[i].k==0)
dfs2(edge[i].v);
edge[i].k=1;
return;
inti,j,a,b,c;
m);
memset(map,0xff,sizeof(map));
c);
if(map[a][b]==-1||map[a][b]>
c)
map[a][b]=map[b][a]=c;
if(map[i][j]!
addedge1(i,j,map[i][j]);
if(spfa()==0||sap_gap()<
2)
Nosolution\n"
else
dfs2
(1);
\n"
SPOJ962IntergalacticMap
题目大意:
在一个无向图中,一个人要从A点赶往B点,之后再赶往C点,且要求中途不能多次经过同一个点。
问是否存在这样的路线。
(3<
=N<
=30011,1<
=M<
=50011)
建模方法:
由于每个点只能走一次,似乎最短路之类的算法不能用,只有往网络流上靠。
将每个点i拆成两个点i’,i’’并加边(i’,i’’,1)就能轻易达到这个目的。
然后我们以B为源点,A、C为汇点,看能否增广两次。
inti,ca,a,b;
freopen("
1.txt"
"
r"
stdin);
ca);
while(ca--)
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