原题地址
题目描述
有 $n$ 个盒子($1<=N<=1000$)围成一个圈,每个盒子有 $a_i$ 个球,所有盒子的球的总数小于等于 $n$。每一次移动,可以把一个球移动到它的一个相邻的盒子内。 现在要使得每个盒子的球数 $<=1$,求最少的移动次数。
输入和输出
输入的第一行为一个整数 $n$。接下来 $n$ 行,每行一个整数 $a_i$,代表第 $i$ 个盒子里球的个数。
输出一个整数,代表最少的移动次数。
样例
输入
12
0
0
2
4
3
1
0
0
0
0
0
1
输出
19
思路
练习网络流的一道题,做完令我对费用流有了更深层次的理解。。。
首先讲一下建图的方法:
超级源点到每个盒子建立一条流量为 $a_i$,费用为 0 的边;
每对相邻的盒子之间建立一条流量为无限,费用为 1 的边;
最后每个盒子到超级汇点建立一条流量为 $1$ ,费用为 0 的边。
在这张图上求最小费用最大流即可。
然后如果你不够理解其中的原因,可以这样理解:
我们将球的移动看做流量,费用为操作次数。初始从源点流出到每个盒子的流量显然就是球的个数 $a_i$,费用为 0。因为我们允许进行的操作是在相邻的盒子之间移动一个球,所以相邻盒子之间建立流量为无限(因为这个操作理论上可以进行无限次),费用为 1 的边。最后因为最后每个盒子只能留下小于等于 1 的球,所以连向汇点的边流量只能为 1。
代码
#include
#define maxn 1010
using namespace std;
int head[maxn],dis[maxn];
int neflow[maxn];
int cnt=1,s,t,n,m;
struct Edge{
int to,next,val,cost;
Edge(int a=0,int b=0,int c=0,int d=0){
to=a,next=b,val=c,cost=d;
}
}l[10010];
struct Pre{
int id,from;
Pre(int a=0,int b=0){
id=a,from=b;
}
}pre[maxn];
inline void Add(int a,int b,int c,int d){
l[++cnt]=Edge(b,head[a],c,d);head[a]=cnt;
l[++cnt]=Edge(a,head[b],0,-d);head[b]=cnt;
}
inline bool Spfa(){
queueq;
fill(dis+1,dis+2+n,INT_MAX);
q.push(s);
dis[s]=0;
neflow[s]=0x3f3f3f3f;
while(!q.empty()){
int hq=q.front();q.pop();
for (int i=head[hq];i;i=l[i].next){
int v=l[i].to;
if (dis[v]>dis[hq]+l[i].cost&&l[i].val){
dis[v]=dis[hq]+l[i].cost;
pre[v]=Pre(i,hq);
neflow[v]=min(neflow[hq],l[i].val);
q.push(v);
}
}
}
return dis[t]!=INT_MAX;
}
void EK(){
int mincost=0;
int maxflow=0;
while(Spfa()){
int tem=neflow[t];
for (register int i=t;i!=s;i=pre[i].from)
l[pre[i].id].val-=tem,
l[pre[i].id^1].val+=tem;
mincost+=tem*dis[t];
maxflow+=tem;
}
printf("%d",mincost);
}
int main(){
scanf("%d",&n);
t=n+1;
for (register int i=1;i<=n;i++){
int x;
scanf("%d",&x);
Add(s,i,x,0);
Add(i,t,1,0);
}
for (int i=1;i