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;
}

 

转载于:https://www.cnblogs.com/north_dragon/archive/2010/06/10/1755904.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值