【费用流】[SDOI2009]晨跑

链接

https://www.luogu.org/problemnew/show/P2153

大意

有一张有向图,有 m 条边,在只希望每条边只走一次的情况下,使经过路径长度的和最小

思路

用网络流来处理每条边只走一次的要求,既然要求只走一次,那么它的容量即为1,我们把 i 点拆成两个点入点和出点,他们之间的容量为1,代价为0,而在有路的两个点间再建边,容量为1,代价为这条路的长度,然后把源点和起点相连,容量为无穷大,终点和汇点相连,容量也是无穷大,再跑一遍最小费用最大流即为答案

代码

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define N 5001
#define M 50001
using namespace std;int f,n,m,s,t,ans1,ans2,u,v,w,g;char c;
int read()
{
    f=0;
    while(c=getchar(),c<=47||c>=58);f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
    return f;
}
struct node{int next,to,w,g;}e[M<<1];
int dis[N],l[N],tot,pos[N];
bool vis[N];
void add(int u,int v,int w,int c)
{
    e[tot]={l[u],v,w,c};l[u]=tot++;
    e[tot]={l[v],u,0,-c};l[v]=tot++;
    return;
}
bool spfa()
{
    fill(dis+1,dis+1+t,50234567);
    memset(vis,0,sizeof(vis));
    queue<int>q;q.push(s);dis[s]=0;vis[s]=true;
    while(q.size())
    {
        int x=q.front();q.pop();vis[x]=true;
        for(int i=l[x];~i;i=e[i].next)
        {
            int y=e[i].to,w=e[i].g;
            if(e[i].w&&dis[y]>dis[x]+w)
            {
                dis[y]=dis[x]+w;
                pos[y]=i;
                if(!vis[y]) q.push(y),vis[y]=true;
            }
        }
        vis[x]=false;
    }
    return dis[t]<50234567;
}
void updata()
{
    int x=t;
    while(x!=s)
    {
        int i=pos[x];
        e[i].w--;
        e[i^1].w++;
        x=e[i^1].to;
    }
    ans1++;
    ans2+=dis[t];
    return;
}
void EK()
{
    while(spfa()) 
    updata();return;
}
int main()
{
    memset(l,-1,sizeof(l));
    n=read();m=read();s=1;t=n+n;
    add(s,n+1,1e9,0);add(n,t,1e9,0);//终点和起点与源点和汇点相连
    for(int i=2;i<n;i++) add(i,i+n,1,0);//中间的入点和出点相连
    for(int i=1,u,v,c;i<=m;i++)
    {
        u=read();v=read();c=read();
        add(u+n,v,1,c);//u的出点与v的入点相连
    }
    EK();
    printf("%d %d",ans1,ans2);//输出
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/80771798
今日推荐