POJ 3204
//题目类型:最小割
实际上就是求割边,但这个割边必需是一头能被源到达,另一头能被汇到达。
以下分析转载自:http://hi.baidu.com/edwardmj/blog/item/112891b73f289ac536d3ca80.html
【题目大意】给定一个流网络,让你求其中有多少条有效边。其中有效边的定义是:修改这条边的容量,可以使最大流增大。(只允许修改一条边)
【算法分析】如果你平常写网络流不都是写预留推进的话,这题应该难不倒你。。。
实际上呢,如果你把某一条边的容量增加了的话,如果最大流增大,那么必然是在当前流网络中能找到一条新的增广路(并且该增广路要经过你修改过的那条边)。而最大流的标志则是不能再找到增广路了。
于是我们得到这样一个算法:
选求最大流。
然后在残余网络中找满流边[X,Y](如果是非满流边你增加它容量也没用。。。因为它的容量本来就过多了)
如果在残余网络中存在路径[S,X]和[Y,T],那么这条边为有效边。
为什么?
因为如果你增加[X,Y]的容量,那么在存在增广路径[S,X]->[X,Y]->[Y,T]
这样我们就得到一个求有效边的算法。
但是如果你要Floyed的话。。。我不阻止你。。。
而我们其实存在更高效的方法,那就是先以S为起点DFS,然后将所有边反向,然后以T为起点dfs。
这样总复杂度就是O(maxflow)
#include <stdio.h>
#include <stdlib.h>
//#include <conio.h>
#include <string.h>
const int maxn=501;
const int maxm=5001;
struct node
{
int x,y,f,op,next; //x起点,y终点,f权值,next是以x为起点的上一条边在g中的位置,op是反向边在g中的下标位置
}g[maxm*2];
//first[]存储的是以x为起点的最后一条边的在数组g中的下标
//sumd[]用于记录表示标号为i的顶点数有多少个,用于间隙优化
int first[maxn],now[maxn],sumd[maxn];
int ncount; //代表结点的总数
int dis[maxn],fanhui[maxn],pre[maxn],tot; //dis[]用于记录距离标号,pre[i]记录i的前驱在g[]中的位置,tot记录边的总数
bool v[maxn],vs[maxn],vt[maxn];
int map[maxn][maxn];
int n,m;
void add(int x,int y,int c)
{
tot++; //tot记录边的总数
g[tot].x=x;
g[tot].y=y;
g[tot].f=c;
g[tot].op=tot+1; //反向边在g中的下标位置
g[tot].next=first[x]; //记录以x为起点的上一条边在g中的下标位置
first[x]=tot; //以x为起点的边的位置
tot++;
//反向边
g[tot].x=y;
g[tot].y=x;
g[tot].f=0; //反向边的初始网络流为0
g[tot].op=tot-1;
g[tot].next=first[y];
first[y]=tot;
}
//ISAP算法
int maxflow(int src,int des)
{
int i,flow,t,j,tempmin; //i,j用于标识结点,t用于标识结点在g中的位置
bool flag; //用于标识是否找到了允许路径
int sumflow;
memset(dis,0,sizeof(dis));
memset(sumd,0,sizeof(sumd));
for(i=0;i<ncount;i++)
now[i]=first[i];
sumd[0]=ncount; //标号为0的结点有ncount个
sumflow=0;
i=src; //i初始化为起点
flow=10000000;
while(dis[src]<ncount)
{
fanhui[i]=flow;
flag=false;
t=now[i];
while(t!=0) //寻找允许路径
{
j=g[t].y;
if((g[t].f>0)&&(dis[j]+1==dis[i])) //允许弧
{
flag=true;
pre[j]=t;
now[i]=t;
if(g[t].f<flow) //找到允许增量
flow=g[t].f;
i=j;
if(i==des) //找到了允许路径
{
sumflow+=flow;
while(i!=src) //修改残余网络
{
g[pre[i]].f-=flow; //正向边
g[g[pre[i]].op].f+=flow; //反向边
i=g[pre[i]].x;
}
flow=10000000;
}
break;
}
t=g[t].next;
}
if(flag)
continue;
//没有找到允许路径
tempmin=ncount-1;
t=first[i];
while(t!=0)
{
if((g[t].f>0)&&(dis[g[t].y]<tempmin))
{
tempmin=dis[g[t].y];
now[i]=t;
}
t=g[t].next;
}
sumd[dis[i]]--;
if(sumd[dis[i]]==0) break; //间隙优化
dis[i]=tempmin+1; //此处别忘+1,因为d[i]=tempmin{d[j]+1|(i,j)在残留网络中}
sumd[dis[i]]++;
if(i!=src)
{
i=g[pre[i]].x;
flow=fanhui[i];
}
}
return sumflow;
}
void init()
{
tot = 0;
memset(first,0,sizeof(first));
scanf("%d%d",&n,&m);
ncount = n;
for (int i=0;i<m;i++)
{
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
add(x,y,c);
}
maxflow(0,n-1);
}
void dfs(int k) //求从k出发可以到达的所有顶点
{
v[k]=1;
for (int i=0;i<n;i++)
if (!v[i] && map[k][i]) dfs(i);
}
void work()
{
memset(map,0,sizeof(map));
for(int i=1;i<=tot;i+=2) //遍历所有的边(i+=2,是因为是使用邻接表存储,下一条边存得是反向边)
if(g[i].f) map[g[i].x][g[i].y]=1; //如果该条边不是满流
memset(v,0,sizeof(v));
dfs(0); //从0开始搜索
memcpy(vs,v,sizeof(v)); //void *memcpy( void *dest, const void *src, size_t count );
memset(map,0,sizeof(map));
for (int i=1;i<=tot;i+=2) //遍历所有的边
if (g[i].f) map[g[i].y][g[i].x]=1;
memset(v,0,sizeof(v));
dfs(n-1); //从n-1开始搜索
memcpy(vt,v,sizeof(v));
int ans=0;
for (int i=1;i<=tot;i+=2) //遍历所有的边
if (vs[g[i].x] && vt[g[i].y] && !g[i].f) //g[i].c==0代表该边是满流,并且边的起点与源点有连接,边的终点与汇点有连接
ans++;
printf("%d\n",ans);
}
int main()
{
//freopen("1.txt","r",stdin);
init();
work();
//getch();
return 0;
}