Dual Core CPU POJ 3469(邻接表+GAP+CUR优化)

 RE了好几次,后来发现是边开小了,这个模板又是达哥的,我继承了达哥的模板数据。。。

SAP算法  最短增广路算法(Shortest Augmenting Path Algorithm),是网络流中求最大流的经典算法之一,即每次寻找包含弧的个数最少的增广路进行增广,可以证明,此算法最多只需要进行mn/2次增广。并且引入距离标号的概念,可以在O(n)的时间里找到一条最短增广路。最终的时间复杂度为O(n^2m),但在实践中,时间复杂度远远小于理论值(特别是加了优化之后),因此还是很实用的。

距离标号:

  对于每个顶点i赋予一个非负整数值d(i)来描述i到t的“距离”远近,称它为距离标号,并且满足以下两个条件: 1. d(t)=0 2. 对于残留网络Gf中的一条弧(i,j),d(i)≤d(j)+1。

  允许弧和允许路:

  如果残留网络Gf中的一条弧(i,j)满足d(i)=d(j)+1,我们称(i,j)是允许弧,由允许弧组成的一条s-t路径是允许路。显然,允许路是残留网络Gf中的一条最短增广路。当找不到允许路的时候,我们需要修改某些点的d(i)。

Gap优化:

  我们可以注意到由于残留网络的修改只会使d(i)越来越大(因为修改前d(i)<d(j)+1,而修改后会存在d(i)=d(j)+1,因此变大了),所以说d(i)是单调递增的,这就提示我们,如果d函数出现了“断层”,即没有d(i)=k,而有d(i)=k±1,这时候必定无法再找到增广路径。我们可以这么想,现在的i满足d(i)=k+1,发现没有一个d(j)为k,因此就会尝试去调整d(i),但是d(i)是单调递增的,只会越来越大,所以k这个空缺便永远不会被补上,也就是说无法再找到增广路径。

当前弧优化:

  可以注意到一个事实:如果说在某次迭代中从i出发的弧(i,j)不是允许弧,则在顶点i的标号修改之前(i,j)都不可能是允许弧。(因为d(i)不变,d(j)不减且d(i)<d(j)+1)这样,在查找允许弧的时候只需要从上一次找到的允许弧开始找。所以我们增加“当前弧”这个数据结构,记录当前顶点找到的允许弧,只有在修改这个顶点标号时才会更改这个顶点的当前弧。

 #include <cstdio>
#include <string.h>
const int N=20005;
const int M=890000;//边是双向存的(注意不是无向)要开正常的2倍大
const int inf=0x7fffffff;
int head[N];
struct Edge{
    int v,next,w;
} edge[M];
int cnt,n,s=0,t;
void addedge(int u,int v,int w)//这里存的还是一条有向边 
{
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
    edge[cnt].v=u;
    edge[cnt].w=0;
    edge[cnt].next=head[v];
    head[v]=cnt++;
}
int sap(){
    int pre[N],cur[N],dis[N],gap[N];
    int flow=0,aug=inf,u;
    bool flag;
    for(int i=0; i<n; i++){
        cur[i]=head[i];
        gap[i]=dis[i]=0;
    }
    gap[s]=n;
    u=pre[s]=s;
    while(dis[s]<n){
        flag=0;
        for(int &j=cur[u]; j!=-1; j=edge[j].next){
            int v=edge[j].v;
            if(edge[j].w>0&&dis[u]==dis[v]+1){
               flag=1;
               if(edge[j].w<aug) aug=edge[j].w;
               pre[v]=u;
               u=v;
               if(u==t){
                    flow+=aug;
                    //printf("%d\n",flow);
                    while(u!=s){
                       u=pre[u];
                       edge[cur[u]].w-=aug;
                       edge[cur[u]^1].w+=aug;//异或是找与其配对的边 
                    }
                    aug=inf;
                }
                break;
            }
        }
        if(flag) continue;
        int mindis=n;
        for(int j=head[u]; j!=-1; j=edge[j].next){
            int v=edge[j].v;
            if(edge[j].w>0&&dis[v]<mindis){
                mindis=dis[v];
                cur[u]=j;
            }
        }
        if((--gap[dis[u]])==0)
            break;
        gap[dis[u]=mindis+1]++;
        u=pre[u];
    }
    return flow;
}
int  main(){
    int m,a,b,u,v,w;
    while(~scanf("%d%d",&n,&m))
    {
        n++;
        t=n;
        cnt=0;
        memset (head , -1 , sizeof(head));
        for (int i=1 ; i<n ; ++i)
        {
            scanf("%d%d",&a,&b);
            addedge(0,i,a);
            addedge(i,t,b);
        }
        for (int i=0 ; i<m ; ++i)
        {
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }
        n++;
        printf("%d\n",sap());
    }
    return 0;
}


 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值