【bzoj 2502】清理雪道(有上下界的网络流)
Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 524 Solved: 284
[Submit][Status][Discuss]
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福建集训
【题解】 【有上下界的网络流】
**【有上下界的网络流之最小流模板:先考虑正常的建图,因为当前题可以从任意节点开始结束,所以所有点与源汇点连边。但是因为有上下界,所以将流量分为两部分:必须流满的和可以自由流的部分。可以自由流的部分可以转化为和普通网络流一样的只有上界的网络流,而上界是上界和下界的差,然后跑最大流即可;现在来考虑必须流满的部分,即目前缺失的部分,把每个点少了流入的流量,作为源点向当前点的边,把多流入的流量用一条当前点到汇点的边导出,以保证网络里流量平衡。因为这里的边是用来调整流量平衡的,所以不能与原图的原汇点连边,所以再建一对新的源汇点。
在跑有上下界的网络流的最小流时,要跑两遍最大流,第一遍是要跑所有必须满的部分和自由流量部分,所以以用来调整流量时加入的一对源汇点作为起点和终点,将原来的图的源汇点连一条汇点到源点的流量为INF的边,将图变为有环图,然后找有没有可行流,这时的可行流就是原汇点到源点的那条边,找到后把环破掉
然后再跑一边最大流,因为刚开始跑可行流的时候跑的是最大流,所以可能会多跑,而我们求的是原图的最小流,所以先将所有的补偿流删掉,跑最大流,退掉大多数的流量】**
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define INF 0x7fffffff
using namespace std;
int a[100010],next[100010],remain[100010],p[210],tot;
int dis[210],cur[210];
int n,f[210],S,T,SS,TT,ans;
inline void add(int x,int y,int flow)
{
tot++; a[tot]=y; next[tot]=p[x]; p[x]=tot; remain[tot]=flow;
tot++; a[tot]=x; next[tot]=p[y]; p[y]=tot; remain[tot]=0;
}
inline bool bfs(int s)
{
queue<int>que;
memset(dis,-1,sizeof(dis));
for(int i=s;i<=TT;++i) cur[i]=p[i];
dis[s]=0; que.push(s);
while(!que.empty())
{
int u,v;
u=que.front(); que.pop();
v=p[u];
while(v!=-1)
{
if (remain[v]&&dis[a[v]]<0)
{
dis[a[v]]=dis[u]+1;
que.push(a[v]);
}
v=next[v];
}
}
if (dis[T]<0) return false;
else return true;
}
inline int dfs(int now,int flow)
{
if(!flow||now==T) return flow;
int u=cur[now],s1=0,s;
while(u!=-1)
{
cur[now]=u;
if (dis[a[u]]==dis[now]+1&&(s=dfs(a[u],min(flow,remain[u]))))
{
s1+=s; flow-=s;
remain[u]-=s; remain[u^1]+=s;
if (!flow) break;
}
u=next[u];
}
return s1;
}
//上面完全就是DINIC跑最大流的模板,关键在主程序!!!!
int main()
{
int i,j;
memset(p,-1,sizeof(p));
memset(next,-1,sizeof(next));
scanf("%d",&n); tot=-1;
S=0; T=n+1; SS=T+1; TT=SS+1;//预处理2个源点和2个汇点
for(i=1;i<=n;++i)
{
int m,a;
scanf("%d",&m);
for(j=1;j<=m;++j)
{
scanf("%d",&a);
f[i]--; f[a]++;//统计每个点失去的流量
add(i,a,INF);//把自由流量加入图中
}
}
for(i=1;i<=n;++i)
if (f[i]>0) add(S,i,f[i]);//补充流量
else add(i,T,-f[i]);//把多出的流量减回去
add(TT,SS,INF);//消除源汇点
for(i=1;i<=n;++i) //把原图上的点加入图中,这里与一般的网络流建图是一样的意思(因为可以从任意点出发,所以超级源点与所有点连边,所有点向超级汇点连边),只有一个地方稍有不同,即此时的流量是上界减下界所得
add(SS,i,INF),add(i,TT,INF);
int r;
while(bfs(S))
while(r=dfs(S,INF));//求一条可以跑完全图的路径
int u=p[S];
while(u!=-1)//删除附加的流量
{
remain[u]=remain[u^1]=0;
u=next[u];
}
u=p[T];
while(u!=-1)
{
remain[u]=remain[u^1]=0;
u=next[u];
}
u=p[SS];
while(u!=-1)
{
if (a[u]==TT)
{ans=remain[u]; remain[u]=remain[u^1]=0; break;}//删除那条成环边
u=next[u];
}//找到那条满足要求的可行流路径
add(S,TT,INF); add(SS,T,INF);
while(bfs(S))//由于跑可行流按最大流跑,所以可能多跑了,所以在求最小流的时候,要在原图中退流,因为是求最小流,所以要退最多的流量,即再跑一遍最大流
while(r=dfs(S,INF))
ans-=r;
printf("%d\n",ans);
return 0;
}