「题解」P4553 80人环游世界

一眼丁真,鉴定为费用流。

思路

类似于路径覆盖问题。

考虑把每个点拆成 入点 \(x\)出点 \(y\)

对于每个点的 入点 \(x\) 都向这个点的 出点 \(y\) 连一条 容量为\(V_i\),费用为\(0\) 的边来控制每个点会被访问 \(V_i\) 次。

然后建一个 中间点 \(p\),连一条 \(s\Rightarrow p\) 容量为 \(m\),费用为 \(0\) 的边来控制总人数为 \(m\) 人。

\(p\) 向每个点的 入点 \(x\) 连一条容量为 \(inf\),费用为 \(0\) 的边。

从每个点的 出点 \(y\)汇点 \(t\) 连一条容量为 \(inf\),费用为 \(0\) 的边。

对于每一条边 \((u,v,c)\),建一条 \(u_y\Rightarrow v_x\) 容量为 \(inf\),费用为 \(c\) 的边。

跑一边最小费用最大流。

然后你就会发现你 \(Wa\) 的很惨。

为什么呢?

把剩余容量输出出来会发现:有些点没有被经过 \(V_i\) 次,也就是每个点的 入点 \(x\)出点 \(y\) 连的边有剩余容量。

这是与题目描述不符的,思考我们建边的过程,发现我们只控制了入点 \(x\)出点 \(y\) 的上界,但其实这条边的上下界都应该为 \(V_i\)

考虑上下界网络流。

但我们不会啊 相信咱们都是懒得写啊

所以现在得想办法让所有 入点 \(x\)出点 \(y\) 连的边都为满流

想到我们用的是最小费用最大流,我们可以让这些边的花费都为 \(-inf\),这样就能保证这些边一定为满流了。

我们定义最小费用为 \(ans\)

最后的答案为:\(ans+ \sum\limits_{i = 1}^{n} V_i \times inf\)

代码

#include<bits/stdc++.h>
using namespace std;
const int MX_N=5010,MX_M=50100,INF=0x3f3f3f3f,inf=1e6+10;//注意INF一定要大于inf,要不会死循环。
struct node{
    int next,to;
    int w,c;
}edge[MX_M<<1];
int head[MX_N]={0},edge_cnt=0;
inline void Add(int x,int y,int w,int c){
    node &i=edge[edge_cnt];
    i.w=w,i.c=c,i.to=y,i.next=head[x];
    head[x]=edge_cnt++;
}
inline void add(int x,int y,int w,int c){
    Add(x,y,w,c),Add(y,x,0,-c);
}
int dist[MX_N]={0},lim[MX_N]={0},pre[MX_N];
bool vis[MX_N]={0};
int s=0,t=MX_N-1;
bool spfa(){
    memset(dist,INF,sizeof(dist));memset(lim,0,sizeof(lim));memset(vis,0,sizeof(vis));
    queue<int >qu;qu.push(s);
    vis[s]=1,lim[s]=INF,dist[s]=0;
    while(!qu.empty()){
        int now=qu.front();qu.pop();vis[now]=0;
        for(int i=head[now];~i;i=edge[i].next){
            int to=edge[i].to,w=edge[i].w,c=edge[i].c;
            if(dist[to]>dist[now]+c&&w){
                dist[to]=dist[now]+c;
                pre[to]=i;
                lim[to]=min(lim[now],w);
                if(!vis[to]){
                    qu.push(to);
                    vis[to]=1;
                }
            }
        }
    }
    return lim[t]>0;
}
void EK(int &flow,int &cost){
    flow=cost=0;
    while(spfa()){
        flow+=lim[t];
        cost+=lim[t]*dist[t];
        for(int i=t;i!=s;i=edge[pre[i]^1].to){
            edge[pre[i]].w-=lim[t];
            edge[pre[i]^1].w+=lim[t];
        }
    }
}
signed main(){
    memset(head,-1,sizeof(head));
    //=======================================
    int n,m;scanf("%d%d",&n,&m);
    int p=t-2,sum=0;add(s,p,m,0);    //建立中间点p
    for(int i=1;i<=n;i++){
        int xi;scanf("%d",&xi);sum+=xi;
        add(i,i+n,xi,-inf);            //保证这一条边一定为满流
        add(p,i,INF,0);add(i+n,t,INF,0);
    }
    for(int i=1;i<n;i++){
        for(int j=1;j<=n-i;j++){
            int xi;scanf("%d",&xi);int to=i+j;
            if(xi==-1)  continue;
            add(i+n,to,INF,xi);
        }
    }
    int cost,flow;EK(flow,cost);
    printf("%d",cost+sum*inf);     //最后答案得去除-inf的影响
    //=======================================
    return 0;
}
posted @ 2024-03-27 21:10  是菜菜呀  阅读(20)  评论(0编辑  收藏  举报