网络流改进SAP算法模版、HDU 1532 Drainage Ditches(解题报告)

转载请注明出处:http://write.blog.csdn.net/postlist

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1532


本次网络流算法看了两天了,先学习了EK算法,发现速度不够快,于是查找资料,得网络流诸多算法中主流算法(SAP算法)学习之。

相关链接:

1、网络流的算法分类:http://www.notonlysuccess.com/index.php/algorithm-of-network/

2、牛人博客,几种算法的分析:http://blog.csdn.net/abcjennifer/article/details/5556455

3、较详细的SAP算法分析:http://hi.baidu.com/hewei156/blog/item/f17038326b2f91c0a2cc2b78.html


今天看了挺久的HUD 3572  http://acm.hdu.edu.cn/showproblem.php?pid=1532    自己用下面的模版写,800多ms。  然后看了别人用邻接表存边时间在100以内!  遂学习之。

  然后,然后重点来了!  我发现这个模版竟然可以去掉宽搜那部分!! 具体原因尚未确定。鉴于明天还要早起,先把代码贴在最后,明天再研究!


研究的一天多的代码模版(该模版正确性及可用性有待证明,以后若出现什么问题将进行修改):

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<queue>
#include<math.h>
#define maxn 205
#define INF 100000000
using namespace std;

int n,m,s,t;// n点数,m边数,s起点,t终点
int map[maxn][maxn],fa[maxn],gap[maxn],level[maxn];//map有向图,fa父节点,
//gap[i]=j表示以汇点距离为i的点有j个,level[i]=j表示点i与汇点距离为j 

void sap_bfs()//反向 BFS 初始化所有顶点的距离标号、初始化level和gap数组 
{
	memset(level,1,sizeof(level));//这个很神奇,每个int的4字节都变成0000 0001
//即数组被初始化为二进制的00000001 00000001 00000001 00000001 ,十进制为16843009 
	memset(gap,0,sizeof(gap));
	queue<int> q;
	q.push(t);
	level[t]=0;//汇点距离为0 
	gap[level[t]]++;
	while(!q.empty())
	{
		int temp=q.front(); q.pop();
		for(int i=1;i<=n;i++)  
		if(level[i]>n && map[i][temp]>0)
		{
			q.push(i);
			level[i]=level[temp]+1;
			gap[level[i]]++;
		}
	}
}

int fine_path(int u)//查找残量网络中是否有与u相连 且距离为1的点 
{
	for(int i=1;i<=n;i++) if(map[u][i]>0 && level[u]==level[i]+1)
		return i;
	return -1;
}

int Advance()//找到汇点时进行增广 
{
	int i,minflow=INF;
	for(i=t;i!=s;i=fa[i]) if(minflow>map[fa[i]][i])//查找这条增广路中最小的流量 
	{
		minflow=map[fa[i]][i];
	}
	for(i=t;i!=s;i=fa[i])
	{
		map[fa[i]][i]-=minflow;
		map[i][fa[i]]+=minflow;//反向边也要操作 
	}
	return minflow;
}

int retreat(int u)//对点u重新设置最小的距离 
{
	int fine=INF;
	for(int i=1;i<=n;i++) if(map[u][i]>0 && fine>level[i]+1)
		fine=level[i]+1;
	if(fine==INF) fine=n;//找不到的话设置为最大,即点u不会再被经过 
	return fine;
}

int cxbsap()
{
	sap_bfs();
	int i,j,v,u=s,flow=0;//初始化 
	while(level[s]<=n)//源点与汇点距离小于n时执行,要是不存在源点到汇点的通路直接跳出 
	{
		v=fine_path(u);// 找u的下一个点 
		if(v>0)
		{
			fa[v]=u;//找到后标记父节点 
			u=v;//把v赋值给u,如果不是汇点,直接跳回75行了 
			if(u==t)
			{
				flow+=Advance();
				u=s;//找的一条路径后重新查找 
			}
		}
		else
		{
			if(--gap[level[u]]==0) return flow;//如果其中一个距离个数为0,即出现断层,没有通路看,直接跳出 
			v=retreat(u);
			gap[v]++;
			level[u]=v;
			if(u!=s) u=fa[u];
		}
	}
	return flow;
}

int main()
{
//	freopen("in.txt","r",stdin);
	int i,j,a,b,x;
	while(~scanf("%d%d",&m,&n))
	{
		memset(map,0,sizeof(map));
		for(i=0;i<m;i++)
		{
			scanf("%d%d%d",&a,&b,&x);
			map[a][b]+=x;
		}
		s=1; t=n;
		int ans=cxbsap();
		printf("%d\n",ans);
	}
	return 0;
}



我发现其实自己有点强迫症,我喜欢带参数的函数,所以:

/*
网络流改进sap模版:HDU1532
计算一个点编号1~n的网络的最大流;
时间复杂度o(v*e^2)  v点数,e边数
陈小宾,2012、8、13凌晨 
*/
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<queue>
#include<math.h>
#define maxn 205
#define INF 100000000
using namespace std;

int n,m;// n点数,m边数,s起点,t终点
int map[maxn][maxn],fa[maxn],gap[maxn],level[maxn];//map有向图,fa父节点,
//gap[i]=j表示以汇点距离为i的点有j个,level[i]=j表示点i与汇点距离为j 

void sap_bfs(int t)//反向 BFS 初始化所有顶点的距离标号、初始化level和gap数组 
{
	memset(level,1,sizeof(level));//这个很神奇,每个int的4字节都变成0000 0001
//即数组被初始化为二进制的00000001 00000001 00000001 00000001 ,十进制为16843009 
	memset(gap,0,sizeof(gap));
	queue<int> q;
	q.push(t);
	level[t]=0;//汇点距离为0 
	gap[level[t]]++;
	while(!q.empty())
	{
		int temp=q.front(); q.pop();
		for(int i=1;i<=n;i++)  
		if(level[i]>n && map[i][temp]>0)
		{
			q.push(i);
			level[i]=level[temp]+1;
			gap[level[i]]++;
		}
	}
}

int fine_path(int u)//查找残量网络中是否有与u相连 且距离为1的点 
{
	for(int i=1;i<=n;i++) if(map[u][i]>0 && level[u]==level[i]+1)
		return i;
	return -1;
}

int Advance(int s,int t)//找到汇点时进行增广 
{
	int i,minflow=INF;
	for(i=t;i!=s;i=fa[i]) if(minflow>map[fa[i]][i])//查找这条增广路中最小的流量 
	{
		minflow=map[fa[i]][i];
	}
	for(i=t;i!=s;i=fa[i])
	{
		map[fa[i]][i]-=minflow;
		map[i][fa[i]]+=minflow;//反向边也要操作 
	}
	return minflow;
}

int retreat(int u)//对点u重新设置最小的距离 
{
	int fine=INF;
	for(int i=1;i<=n;i++) if(map[u][i]>0 && fine>level[i]+1)
		fine=level[i]+1;
	if(fine==INF) fine=n;//找不到的话设置为最大,即点u不会再被经过 
	return fine;
}

int cxbsap(int s,int t)
{
	sap_bfs(t);
	int i,j,v,u=s,flow=0;//初始化 
	while(level[s]<=n)//源点与汇点距离小于n时执行,要是不存在源点到汇点的通路直接跳出 
	{
		v=fine_path(u);// 找u的下一个点 
		if(v>0)
		{
			fa[v]=u;//找到后标记父节点 
			u=v;//把v赋值给u,如果不是汇点,直接跳回75行了 
			if(u==t)
			{
				flow+=Advance(s,t);
				u=s;//找的一条路径后重新查找 
			}
		}
		else
		{
			if(--gap[level[u]]==0) return flow;//如果其中一个距离个数为0,即出现断层,没有通路看,直接跳出 
			v=retreat(u);
			gap[v]++;
			level[u]=v;
			if(u!=s) u=fa[u];
		}
	}
	return flow;
}

int main()
{
//	freopen("in.txt","r",stdin);
	int i,j,a,b,x;
	while(~scanf("%d%d",&m,&n))
	{
		memset(map,0,sizeof(map));
		for(i=0;i<m;i++)
		{
			scanf("%d%d%d",&a,&b,&x);
			map[a][b]+=x;
		}
		int ans=cxbsap(1,n);
		printf("%d\n",ans);
	}
	return 0;
}

 ok,此文先到此为止,还要挂题目,明天早起!!  睡觉,cxb,晚安,加油!



不用宽搜的代码,只要初始化两个数组就可以了:


#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<queue>
#include<math.h>
#define maxn 205
#define INF 100000000
using namespace std;

int n,m,s,t,ans;// n点数,m边数,s起点,t终点 
int map[maxn][maxn],fa[maxn],gap[maxn],level[maxn];

int fine_path(int u)
{
    for(int i=1;i<=n;i++) if(map[u][i]>0 && level[u]==level[i]+1)
        return i;
    return -1;
}

int Advance()
{
    int i,minflow=INF;
    for(i=t;i!=s;i=fa[i]) if(minflow>map[fa[i]][i])
    {
        minflow=map[fa[i]][i];
    }
    for(i=t;i!=s;i=fa[i])
    {
        map[fa[i]][i]-=minflow;
        map[i][fa[i]]+=minflow;
    }
    return minflow;
}

int retreat(int u)
{
    int fine=INF;
    for(int i=1;i<=n;i++) if(map[u][i]>0 && fine>level[i]+1)
        fine=level[i]+1;
    if(fine==INF) fine=n;
    return fine;
}

int cxbsap()
{
    int i,j,v,u=s,flow=0;
    while(level[s]<=n)
    {
        v=fine_path(u);
        if(v>0)
        {
            fa[v]=u;
            u=v;
            if(u==t)
            {
                flow+=Advance();
                u=s;
            }
        }
        else
        {
            if(--gap[level[u]]==0) return flow;
            v=retreat(u);
            gap[v]++;
            level[u]=v;
            if(u!=s) u=fa[u];
        }
    }
    return flow;
}

int main()
{
//    freopen("in.txt","r",stdin);
    int i,j,a,b,x;
    while(~scanf("%d%d",&m,&n))
    {
		memset(map,0,sizeof(map)); 
		memset(level,0,sizeof(level));
  		memset(gap,0,sizeof(gap));
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&x);
            map[a][b]+=x;
        }
        s=1; t=n;
        ans=cxbsap();
        printf("%d\n",ans);
    }
    return 0;
}


  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值