BZOJ1877(洛谷P2153)[SDOI2009]晨跑

292 篇文章 1 订阅
281 篇文章 1 订阅

费用流

BZOJ题目传送门

洛谷题目传送门

考虑这么建图:
①因为每个点只能访问一次,因此考虑进行拆点,把每个点拆成入点x与出点y(1和n除外),在x与y之间建一条容量为1,距离为0的边。
②对于从1开始的点,建一条1–>i.x容量为1的边。
③对于指向n的点,建一条i.y–>n容量为1的边。
④对于其他点u,v,建一条u.y–>v.x容量为1的边。

最后跑一遍费用流即可,最大流的答案即为天数,费用即为路程和。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 400
#define MAXM 40000
using namespace std;
struct edge{
    int next,to,dis,v,flow;
};
struct father{
    int x,e;
};
int n,m,k;
int h[MAXN+5],dis[MAXN+5],rem[MAXN+5],que[2*MAXM+5];
bool f[MAXN+5];
edge ed[2*MAXM+5];
father fa[MAXN+5];
int spfa(){
    memset(f,false,sizeof(f));
    memset(dis,0x3f3f3f3f,sizeof(dis));
    dis[1]=0; que[1]=1; rem[1]=0x7fffffff;
    int r=0,w=1;
    while (r<w){
        int x=que[++r];
        f[x]=false;
        for (int i=h[x];~i;i=ed[i].next)
            if (ed[i].v>ed[i].flow&&dis[ed[i].to]>dis[x]+ed[i].dis){
                int v=ed[i].to;
                dis[v]=dis[x]+ed[i].dis;
                fa[v].x=x; fa[v].e=i;
                rem[v]=min(rem[x],ed[i].v-ed[i].flow);
                if (!f[v]){ f[v]=true; que[++w]=v;  }
            }
    }
    if (dis[n]==0x3f3f3f3f) return 0;
    return rem[n];
}
void change(int sum){
    int now=n;
    while (now!=1){
        int e=fa[now].e;
        ed[e].flow+=sum; ed[e^1].flow-=sum; now=fa[now].x;
    }
}
void mcmf(){
    int ans=0,cst=0;
    while (1){
        int sum=spfa();
        if (!sum) { printf("%d %d\n",ans,cst); return;  }
        ans++; cst+=sum*dis[n]; change(sum);
    }
}
void addedge(int x,int y,int z,int w){
    ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; ed[k].dis=w; h[x]=k++;
    ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; ed[k].dis=-w; h[y]=k++;
}
int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    for (int i=1;i<=m;i++){
        int u,v,d;
        scanf("%d%d%d",&u,&v,&d);
        if (u!=1&&v!=n) addedge(u+n,v,1,d);
        if (u==1&&v!=n) addedge(u,v,1,d);
        if (u!=1&&v==n) addedge(u+n,v,1,d);
        if (u==1&&v==n) addedge(u,v,1,d);
    }
    for (int i=2;i<n;i++)
        addedge(i,i+n,1,0);
    mcmf();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值