【AHOI2014/JSOI2014】支线剧情

有源汇上下界最小费用可行流

题目大意:

给定一张有向无环图,边带边权。要求使用若干条从 $1$ 出发的链,覆盖所有的边,且使得总长度最短。求最短总长度。

题解:

这个路径覆盖,相当于网络流中,从 $1$ 出发流一条路。

而每条边至少要经过一次,即有容量的下界。

那么要求长度最小,只需要求有下界的最小费用可行流。

关于上下界可行流的建图,大概就是分以下步骤:

  1. 如果原来有源汇,则从汇点到源点连容量 $+\infty$ 的边,转化为无源汇问题。
  2. 对每条限制为 $[L,R]$ 的边,在网络流图中容量为 $R-L$,费用为原来的费用。并记录 $d[i]$ 表示 $i$ 的入边的下界之和减去 $i$ 的出边的下界之和。
  3. 新建源汇(和原来有的源汇不同)。对于点 $i$,若 $d[i]>0$,则需要补流,从源点向 $i$ 连容量 $d[i]$,费用 $0$ 的边;若 $d[i]<0$,则需要减去流量,从 $i$ 向汇点连容量 $-d[i]$,费用 $0$ 的边。

然后跑网络流,当且仅当源点的出边和汇点的入边都满流时,存在可行流。

本题的边只有下界没有上界,令上界为 $+\infty$ 即可。

Code:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=320,inf=0x3f3f3f3f;
int n,head[N],cnt=1,T,TT,iter[N],dis[N],dlt[N],ans;
bool vis[N];
struct edge{
    int to,nxt,cap,cost;
}e[N*N];
inline void addedge(int u,int v,int cap,int cost){
    e[++cnt]=(edge){v,head[u],cap,cost},head[u]=cnt;
    e[++cnt]=(edge){u,head[v],0,-cost},head[v]=cnt;
}
inline void AddEdgE(int u,int v,int L,int R,int cost){
    dlt[u]-=L,dlt[v]+=L,addedge(u,v,R-L,cost);
    ans+=cost*L;
}
queue<int>q;
int dfs(int u,int f){
    if(!f||u==TT)return f;
    vis[u]=1;
    int ret=0;
    for(int&i=iter[u];i;i=e[i].nxt)
        if(e[i].cap&&!vis[e[i].to]&&dis[e[i].to]==dis[u]+e[i].cost){
            int d=dfs(e[i].to,min(f,e[i].cap));
            e[i].cap-=d,e[i^1].cap+=d,f-=d,ret+=d;
            if(!f)break;
        }
    vis[u]=0;
    return ret;
}
bool MCMF(int&cost){
    memset(dis,0x3f,(TT+1)<<2);
    *dis=0;
    for(q.push(0);!q.empty();){
        int u=q.front();q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt)
            if(e[i].cap&&dis[e[i].to]>dis[u]+e[i].cost){
                dis[e[i].to]=dis[u]+e[i].cost;
                if(!vis[e[i].to])vis[e[i].to]=1,q.push(e[i].to);
            }
    }
    if(dis[TT]>1e9)return 0;
    memcpy(iter,head,(TT+1)<<2);
    ans+=dis[TT]*dfs(0,inf);
    return 1;
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    T=n+1,TT=n+2;
    for(int i=1,k;i<=n;++i){
        cin>>k;
        if(k)
            for(int x,y;k--;){
                cin>>x>>y;
                AddEdgE(i,x,1,inf,y);
            }
        AddEdgE(i,T,0,inf,0);
    }
    AddEdgE(T,1,0,inf,0);
    for(int i=1;i<=T;++i)
        if(dlt[i]>0)addedge(0,i,dlt[i],0);else
            if(dlt[i]<0)addedge(i,TT,-dlt[i],0);
    while(MCMF(ans));
    cout<<ans<<'\n';
    return 0;
}