[最小费用最大流] BZOJ2324: [ZJOI2011]营救皮卡丘

题意

这里写图片描述

题解

题目的意思即求K条路覆盖所有节点,走过的总路程的最小值,有一个限制条件就是当某个人从i走到j时,途径的节点都<=j。
对于这个限制条件,我们可以简单的floyd预处理出来,得f[i][j] 为i到j的最短路,且途中的点都<=max(i,j)。
我们需要简化模型:某个人走过的一条路径肯定是分阶段的,也就是说他从某节点i经过一系列合法点到j后,需要停一停等其他人开拓了一些节点再走。我们把某一阶段的一条路径转化成一条直接的边i->j,长度为f[i][j] 。显然这样转化后往回走是没有意义的,这样就转化成了DAG图,而且限制条件已经不用考虑了。
现在问题转化为:用K条路径覆盖一个DAG中的点,路径总长度最小。这个好像是二分图匹配中的经典问题。
下面给出建图方案:
1.建立超级源S与超级汇T,将0~N拆点拆成入点i和出点i’。
2.加边 S->0, 费用0,容量K,表示人数限制
3.对于DAG中的边i->j,加边i->j’, 费用f[i][j], 容量K。(这里容量INF也一样)
4.加边 i’ -> T , 费用0,容量1。
注意到我们把从S到T单位1的流量当做一个人,他走完一个阶段还是能继续走的, 但流量已经流到汇了。为了能保证走完:
5.加边 S -> i , 费用0,容量1。
刷最小费用最大流即可。

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=355, maxe=60005;
int n,m,K,S,T,f[maxn][maxn],fir[maxn],nxt[maxe],tot=1;
int fl[maxn],dis[maxn],pth[maxn];
bool vis[maxn];
queue< int > que;
struct Edge{
    int from,to,cap,flow,cost;
    Edge(int x1=0,int x2=0,int x3=0,int x4=0,int x5=0):from(x1),to(x2),cap(x3),flow(x4),cost(x5){}
} Es[maxe];
void add(int x,int y,int w,int c){
    Es[++tot]=Edge(x,y,w,0,c);
    nxt[tot]=fir[x]; fir[x]=tot;
    Es[++tot]=Edge(y,x,0,0,-c);
    nxt[tot]=fir[y]; fir[y]=tot;
}
bool find_spfa(int S,int T,int &MaxFlow,int &MinCost){
    memset(fl,0,sizeof(fl)); fl[S]=1e+9;
    memset(dis,63,sizeof(dis)); dis[S]=0;
    memset(vis,0,sizeof(vis));
    while(!que.empty()) que.pop();
    que.push(S); pth[S]=0;
    while(!que.empty()){
        int x=que.front(); que.pop();
        vis[x]=false;
        for(int j=fir[x];j;j=nxt[j]) if(Es[j].cap>Es[j].flow&&dis[x]+Es[j].cost<dis[Es[j].to]){
            dis[Es[j].to]=dis[x]+Es[j].cost; pth[Es[j].to]=j;
            fl[Es[j].to]=min(fl[x],Es[j].cap-Es[j].flow);
            if(!vis[Es[j].to]) vis[Es[j].to]=true, que.push(Es[j].to);
        }
    }
    if(!fl[T]) return false;
    MaxFlow+=fl[T]; MinCost+=fl[T]*dis[T];
    for(int j=pth[T];j;j=pth[Es[j].from]){
        Es[j].flow+=fl[T];
        Es[j^1].flow-=fl[T];
    }
    return true;
}
int main(){
    scanf("%d%d%d",&n,&m,&K);
    memset(f,63,sizeof(f));
    for(int i=0;i<=n;i++) f[i][i]=0;
    for(int i=1;i<=m;i++){
        int x,y,z; scanf("%d%d%d",&x,&y,&z);
        f[x][y]=min(f[x][y],z);
        f[y][x]=f[x][y];
    }
    for(int k=0;k<=n;k++)
     for(int i=0;i<=n;i++)
      for(int j=0;j<=n;j++) if(k<=i||k<=j)
       f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

    S=2*n+2; T=2*n+3;
    add(S,0,K,0);
    for(int i=1;i<=n;i++) add(S,i,1,0), add(i+n+1,T,1,0);
    for(int i=0;i<=n-1;i++)
     for(int j=i+1;j<=n;j++) add(i,j+n+1,1e+9,f[i][j]);
    int ans1=0,ans2=0;
    while(find_spfa(S,T,ans1,ans2));
    printf("%d\n",ans2);
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值