[BZOJ2502]清理雪道-有源汇上下界最小流

本文介绍了一种利用网络流算法解决滑雪场雪道清理问题的方法。通过构建有向无环图并应用最小流算法,实现高效清理所有雪道的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

清理雪道

Description

滑雪场坐落在FJ省西北部的若干座山上。
从空中鸟瞰,滑雪场可以看作一个有向无环图,每条弧代表一个斜坡(即雪道),弧的方向代表斜坡下降的方向。
你的团队负责每周定时清理雪道。你们拥有一架直升飞机,每次飞行可以从总部带一个人降落到滑雪场的某个地点,然后再飞回总部。从降落的地点出发,这个人可以顺着斜坡向下滑行,并清理他所经过的雪道。
由于每次飞行的耗费是固定的,为了最小化耗费,你想知道如何用最少的飞行次数才能完成清理雪道的任务。

Input

输入文件的第一行包含一个整数n (2 <= n <= 100) – 代表滑雪场的地点的数量。接下来的n行,描述1~n号地点出发的斜坡,第i行的第一个数为mi (0 <= mi < n) ,后面共有mi个整数,由空格隔开,每个整数aij互不相同,代表从地点i下降到地点aij的斜坡。每个地点至少有一个斜坡与之相连。

Output

输出文件的第一行是一个整数k – 直升飞机的最少飞行次数。

Sample Input

8
1 3
1 7
2 4 5
1 8
1 8
0
2 6 5
0

Sample Output

4

HINT

Source

2011福建集训


哇还真有最小流这种操作……


思路:
考虑网络流,按题意建图。
由于技术原因,显然需要一对源和汇。

发现流量至少为1,于是这是个有源汇上下界网络流。
发现要求流量尽量少,那么这是个有源汇上下界最小流。

最小流的具体做法:
首先搞出一个可行流。
然后,删掉超级源和超级汇,把求解可行流时汇连向源(注意是普通汇和普通源)的正无穷边删去,然后从源向汇跑一边最大流即可~

原理是,显然当前流可行,需要把多余的流退回去,于是倒着跑一边最大流,尽可能多的退回去~
可行流的答案减去倒着跑最大流的答案即为最终结果。

所以上下界网络流系列全都是套路啊~

#include<bits/stdc++.h>
using namespace std;

const int N=209;
const int M=100009;
const int Inf=1e9+7;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline int minn(int a,int b){if(a<b)return a;return b;}

int n,m;
int ex[N];
int s,t,ss,tt;

namespace flows
{
    int to[M<<1],nxt[M<<1],w[M<<1],beg[N],tot=1;
    int dis[N],q[N],cur[N];

    inline void adde(int u,int v,int c)
    {
        to[++tot]=v;
        nxt[tot]=beg[u];
        w[tot]=c;
        beg[u]=tot;
    }

    inline void add(int u,int v,int c)
    {
        adde(u,v,c);adde(v,u,0);
    }

    inline bool bfs(int s,int t) 
    {
        memset(dis,0,sizeof(dis));
        dis[s]=1;q[1]=s;
        for(int l=1,r=1,u=q[l];l<=r;u=q[++l])
            for(int i=beg[u],v;i;i=nxt[i])
                if(!dis[v=to[i]] && w[i]>0)
                    dis[v]=dis[u]+1,q[++r]=v;
        return dis[t];
    }

    inline int dfs(int u,int t,int mflow)
    {
        if(u==t || !mflow)return mflow;
        int cost=0;
        for(int &i=cur[u],v,f;i;i=nxt[i])
            if(dis[v=to[i]]==dis[u]+1 && w[i]>0)
            {
                f=dfs(v,t,minn(mflow-cost,w[i]));
                w[i]-=f;w[i^1]+=f;
                cost+=f;
                if(cost==mflow)break;
            }
        if(!cost)dis[u]=-1;
        return cost;
    }

    inline int dinic(int s,int t)
    {
        int ret=0;
        while(bfs(s,t))
        {
            for(int i=0;i<=n+3;i++)
                cur[i]=beg[i];
            ret+=dfs(s,t,Inf);
        }
        return ret;
    }

    inline void del(int x)
    {
        for(int i=beg[x];i;i=nxt[i])
            w[i]=w[i^1]=0;
    }
}

using namespace flows;

int main()
{
    n=read();
    s=0;t=n+1;
    ss=n+2;tt=n+3;
    for(int i=1;i<=n;i++)
    {
        m=read();
        for(int j=1,v;j<=m;j++)
        {
            v=read();
            ex[i]--;ex[v]++;
            add(i,v,Inf);
        }
    }

    for(int i=1;i<=n;i++)
        add(s,i,Inf);
    for(int i=1;i<=n;i++)
        add(i,t,Inf);

    for(int i=0;i<=n+1;i++)
        if(ex[i]<0)
            add(i,tt,-ex[i]);
        else if(ex[i]>0)
            add(ss,i,ex[i]);
    add(t,s,Inf);

    dinic(ss,tt);
    int ans=w[tot];
    w[tot]=w[tot-1]=0;
    del(ss);del(tt);

    printf("%d\n",ans-dinic(t,s));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值