费用流模板练习

在FallDream大佬的耐心指导下弄懂了原始对偶算法,跟SPFA比起来多不了几行,以后费用流就打它了。打几题练练手。

[USACO 2003 February]Farm Tour

题目大意:n个点m条边的无向图,要求找到一条路径从1走到n再走回1,不能走重复的边,求最短距离。(n<=1000,m<=10000)

思路:相当于从1选两条不相交的路径走到n,权和最小,比较裸的费用流,每条边建成费用为该边的长度,流量为1,S到1号点连费用0,流量2,n号点到T连费用0,流量2,复杂度O(费用流(n,m))。

#include<cstdio>
#include<cstring>
inline int read()
{
    int x;char c;
    while((c=getchar())<'0'||c>'9');
    for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0';
    return x;
}
#define MN 1000
#define MM 20002
#define S MN+1
#define T MN+2
#define INF 0x7FFFFFFF
struct edge{int nx,t,w,c;}e[MM*2+5];
int h[MN+5],en=1,q[MN+5],qn,inq[MN+5],d[MN+5],dis,ans,mk[MN+5];
inline void ins(int x,int y,int w,int c)
{
    e[++en]=(edge){h[x],y,w,c};h[x]=en;
    e[++en]=(edge){h[y],x,0,-c};h[y]=en;
}
inline int next(int x){return x==MN+4?0:x+1;}
inline int prev(int x){return x?x-1:MN+4;}
bool spfa()
{
    int i,j,x;
    memset(d,127,sizeof(d));
    for(d[q[qn=1,i=0]=T]=0;i!=qn;inq[x]=0,i=next(i))for(j=h[x=q[i]];j;j=e[j].nx)
        if(e[j^1].w&&d[x]+e[j^1].c<d[e[j].t])
        {
            d[e[j].t]=d[x]+e[j^1].c;
            if(!inq[e[j].t])if(d[e[j].t]<=d[q[i]])inq[q[i]=e[j].t]=1,i=prev(i);
            else inq[q[qn]=e[j].t]=1,qn=next(qn);
        }
    for(i=1;i<=T;++i)for(j=h[i];j;j=e[j].nx)e[j].c+=d[e[j].t]-d[i];
    return dis+=d[S],d[S]<d[0];
}
int dfs(int x,int r)
{
    if(x==T)return ans+=dis*r,r;
    int u=0,k,i;mk[x]=1;
    for(i=h[x];i;i=e[i].nx)if(!mk[e[i].t]&&e[i].w&&!e[i].c)
    {
        k=dfs(e[i].t,r-u<e[i].w?r-u:e[i].w);
        u+=k;e[i].w-=k;e[i^1].w+=k;
        if(u==r)return r;
    }
    return u;
}
int main()
{
    int n,m,i,x,y;
    n=read();m=read();
    ins(S,1,2,0);ins(n,T,2,0);
    while(m--)x=read(),y=read(),i=read(),ins(x,y,1,i),ins(y,x,1,i);
    while(spfa())do memset(mk,0,sizeof(mk));while(dfs(S,INF));
    printf("%d",ans);
}

餐巾计划问题/[BZOJ]1221 软件开发

题目大意:n天,每天需要ai条餐巾,可以以f元一条的价格买餐巾,或者花fa元洗一条用过的餐巾,a天后可用,或花fb元洗一条用过的餐巾,b天后可用,求最小花费。(n<=1000)

思路:考虑最小费用流,我们把每天拆成两个点,点i表示第i天干净的餐巾,i'表示第i天用过的餐巾,S到i连费用f,流量INF,表示买新的餐巾,S到i'连费用0,流量ai,表示这天新产生了ai条用过的餐巾,点i'向点(i+1)'连费用0,流量INF,表示把用过的餐巾留着不洗备用,点i'向点i+a连费用fa,流量INF的边,表示用一种方式洗餐巾,b同理。复杂度O(费用流(n,n))。下面贴的程序里的建图略有区别,思路大致相同。

#include<cstdio>
#include<cstring>
inline int read()
{
    int x;char c;
    while((c=getchar())<'0'||c>'9');
    for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0';
    return x;
}
#define MV 2000
#define ME 6000
#define S MV+1
#define T MV+2
#define INF 0x7FFFFFFF
struct edge{int nx,t,w,c;}e[ME*2+5];
int h[MV+5],en=1,d[MV+5],q[MV+5],qn,inq[MV+5],mk[MV+5],dis,ans;
inline void ins(int x,int y,int w,int c)
{
    e[++en]=(edge){h[x],y,w,c};h[x]=en;
    e[++en]=(edge){h[y],x,0,-c};h[y]=en;
}
inline int next(int x){return x==MV+4?0:x+1;}
inline int prev(int x){return x?x-1:MV+4;}
bool spfa()
{
    int i,j,x;
    memset(d,127,sizeof(d));
    for(d[q[qn=1,i=0]=T]=0;i!=qn;inq[x]=0,i=next(i))for(j=h[x=q[i]];j;j=e[j].nx)
        if(e[j^1].w&&d[x]+e[j^1].c<d[e[j].t])
        {
            d[e[j].t]=d[x]+e[j^1].c;
            if(!inq[e[j].t])if(d[e[j].t]<=d[q[i]])inq[q[i]=e[j].t]=1,i=prev(i);
            else inq[q[qn]=e[j].t]=1,qn=next(qn);
        }
    for(i=1;i<=T;++i)for(j=h[i];j;j=e[j].nx)e[j].c+=d[e[j].t]-d[i];
    return dis+=d[S],d[S]<d[0];
}
int dfs(int x,int r)
{
    if(x==T)return ans+=dis*r,r;
    int i,k,u=0;mk[x]=1;
    for(i=h[x];i;i=e[i].nx)if(e[i].w&&!e[i].c&&!mk[e[i].t])
    {
        k=dfs(e[i].t,r-u<e[i].w?r-u:e[i].w);
        u+=k;e[i].w-=k;e[i^1].w+=k;
        if(u==r)return u;
    }
    return u;
}
int main()
{
    int n,f,ta,fa,tb,fb,x,i;
    n=read();f=read();ta=read();fa=read();tb=read();fb=read();
    for(i=1;i<=n;++i)
    {
        ins(S,i,INF,0);ins(i,T,x=read(),f);ins(S,i+n,x,0);
        if(i<n)ins(i+n,i+n+1,INF,0);
        if(i-ta>0)ins(i+n-ta,i,x,fa-f);
        if(i-tb>0)ins(i+n-tb,i,x,fb-f);
    }
    while(spfa())do memset(mk,0,sizeof(mk));while(dfs(S,INF));
    printf("%d",ans);
}
原文地址:https://www.cnblogs.com/ditoly/p/MCMF.html