BZOJ 2502: 清理雪道 有源汇上下界最小流

title

BZOJ 2502
LUOGU 4843
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

Source

2011福建集训

analysis

本来想到的模型也就是 \(DAG\) 的最小路径覆盖了。

不过建图比较难受,因为我拿一般方法建图,屏幕调试都调不出来,便含泪看题解了。

于是发现这是道有源汇上下界最小流,这么说,我就明白了,不过看出来了就有些显然了,不好解释。

不过建图求解方式就明显了:

  1. 求原图 \(ss\to tt\)的最大流。
  2. 连边 \(s\to t\),边权为 \(INF\)
  3. \(ss\to tt\)的最大流。
  4. 答案即为边 \(t\to s\)\(INF\)的实际流量。

最后你可以拍着胸脯说道:这就是道模板题!(除了我)。

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10,maxm=1e6+10,inf=0x3f3f3f3f;

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1, ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}

template<typename T>inline void write(T x)
{
    if (!x) { putchar('0'); return ; }
    if (x<0) putchar('-'), x=-x;
    T num=0, ch[20];
    while (x) ch[++num]=x%10+48, x/=10;
    while (num) putchar(ch[num--]);
}

int ver[maxm<<1],edge[maxm<<1],Next[maxm<<1],head[maxn],len=1;
inline void add(int x,int y,int z)
{
    ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
    ver[++len]=x,edge[len]=0,Next[len]=head[y],head[y]=len;
}

int ans,M[maxn];
inline void insert(int x,int y,int up,int low)
{
    add(x,y,up-low);
    if (low) M[y]+=low,M[x]-=low;
}

int s,t,ss,tt;
int dist[maxn];
inline bool bfs()
{
    queue<int>q;
    memset(dist,0,sizeof(dist));
    q.push(s),dist[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x]; i; i=Next[i])
        {
            int y=ver[i];
            if (edge[i] && !dist[y])
            {
                dist[y]=dist[x]+1;
                if (y==t) return 1;
                q.push(y);
            }
        }
    }
    return 0;
}

inline int get(int x,int low)
{
    if (x==t) return low;
    int tmp=low;
    for (int i=head[x]; i; i=Next[i])
    {
        int y=ver[i];
        if (edge[i] && dist[y]==dist[x]+1)
        {
            int a=get(y,min(tmp,edge[i]));
            if (!a) dist[y]=0;
            edge[i]-=a;
            edge[i^1]+=a;
            if (!(tmp-=a)) break;
        }
    }
    return low-tmp;
}

int main()
{
    int n,ans=0;
    read(n);
    ss=0,tt=n+1,s=tt+1,t=s+1;
    for (int i=1,mi; i<=n; ++i)
    {
        insert(ss,i,inf,0);
        insert(i,tt,inf,0);
        read(mi);
        for (int j=1,x; j<=mi; ++j) read(x),insert(i,x,inf,1);
    }
    for (int i=1; i<=tt; ++i)
        if (M[i]>0) add(s,i,M[i]);
        else add(i,t,-M[i]);
    while (bfs()) while (get(s,inf));
    insert(tt,ss,inf,0);
    while (bfs()) ans+=get(s,inf);
    write(ans);
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11327232.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值