题目描述
GG 公司有 nn 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 nn 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。
输入输出格式
输入格式:
文件的第 11 行中有 11 个正整数 nn,表示有 nn 个仓库。
第 22 行中有 nn 个正整数,表示 nn 个仓库的库存量。
输出格式:
输出最少搬运量。
输入输出样例
输入样例#1: 复制
5
17 9 14 16 4
输出样例#1: 复制
11
最大流为达成目标 最小费用为答案
连边策略:
如果该值大于平均值 源点连之 费用为0
如果小于平均值 连到汇点 费用也为0
之间两两为inf 费用为0
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
//input by bxd
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i>=(b);--i)
#define RI(n) scanf("%d",&(n))
#define RII(n,m) scanf("%d%d",&n,&m)
#define RIII(n,m,k) scanf("%d%d%d",&n,&m,&k)
#define RS(s) scanf("%s",s);
#define ll long long
#define pb push_back
#define CLR(A,v) memset(A,v,sizeof A)
//////////////////////////////////
#define inf 0x3f3f3f3f
const int N=10000;
const int maxn=2000;
bool vis[maxn];
int n,m,s,t,x,y,z,f,dis[maxn],pre[maxn],last[maxn],flow[maxn],maxflow,mincost;
//dis最小花费;pre每个点的前驱;last每个点的所连的前一条边;flow源点到此处的流量
struct Edge{
int to,next,flow,dis;//flow流量 dis花费
}edge[maxn];
int head[maxn],num_edge;
queue <int> q;
void init()
{
CLR(head,-1);num_edge=-1;
}
void add_edge(int from,int to,int flow,int dis)
{
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
edge[num_edge].flow=flow;
edge[num_edge].dis=dis;
head[from]=num_edge;
edge[++num_edge].next=head[to];
edge[num_edge].to=from;
edge[num_edge].flow=0;
edge[num_edge].dis=-dis;
head[to]=num_edge;
}
bool spfa(int s,int t)
{
memset(dis,0x7f,sizeof(dis));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
q.push(s); vis[s]=1; dis[s]=0; pre[t]=-1;
while (!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
for (int i=head[now]; i!=-1; i=edge[i].next)
{
if (edge[i].flow>0 && dis[edge[i].to]>dis[now]+edge[i].dis)//正边
{
dis[edge[i].to]=dis[now]+edge[i].dis;
pre[edge[i].to]=now;
last[edge[i].to]=i;
flow[edge[i].to]=min(flow[now],edge[i].flow);//
if (!vis[edge[i].to])
{
vis[edge[i].to]=1;
q.push(edge[i].to);
}
}
}
}
return pre[t]!=-1;
}
void MCMF()
{
while (spfa(s,t))
{
int now=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while (now!=s)
{//从源点一直回溯到汇点
edge[last[now]].flow-=flow[t];//flow和dis容易搞混
edge[last[now]^1].flow+=flow[t];
now=pre[now];
}
}
}
int a[maxn];
int main()
{
int sum=0;
init();
RI(n);rep(i,1,n)RI(a[i]),sum+=a[i];
sum/=n;
s=208;t=209;
rep(i,1,n)
{
if(a[i]>sum)add_edge(s,i,a[i]-sum,0);
else if(a[i]<sum) add_edge(i,t,-(a[i]-sum),0);
}
rep(i,2,n-1)
add_edge(i,i+1,inf,1),add_edge(i,i-1,inf,1);
add_edge(1,2,inf,1);add_edge(1,n,inf,1);
add_edge(n,n-1,inf,1);add_edge(n,1,inf,1);
MCMF();
cout<<mincost;
}
View Code
贪心:转自洛谷巨佬 five20
先来讲下普通均分纸牌问题:
普通均分纸牌问题就是nn个小朋友排成一列,各自有a[i]a[i]张牌,每个人只能给相邻的人传递纸牌,问至少需要传递多少张纸牌才能使每个小朋友牌的个数相等。
设总牌数为sumsum(即sum=\sum{a[i]}sum=∑a[i]),则每个人最后会各自有T=\frac{sum}{n}T=nsum张牌,设g[i]=T-a[i]g[i]=T−a[i],则让前kk个人牌数相同需要的交换牌数为\sum\limits_{i=1}^{i\leq k}{|s[i]|}i=1∑i≤k∣s[i]∣,其中s[i]=\sum\limits_{j=1}^{j\leq i}{g[i]}s[i]=j=1∑j≤ig[i],可以这样理解,要让前kk个人牌数相同,要依次让前1,2,3…k-11,2,3…k−1个人牌数相同,多退少补,会与后边的人发生二者之差绝对值的牌数交换。所以移动总牌数ans=\sum{|s[i]|}ans=∑∣s[i]∣。
再来讲下本题的环形均分纸牌问题:
环形均分纸牌问题就是nn个小朋友围成了一圈(等同于第一人和最后一人相邻),这样的话其实可以同样的处理。
仔细思考环形均分纸牌问题可以发现一个性质:必定至少有两个相邻的人不需要从别人那里获得纸牌(这是显然的,不妨设这两个人的位置为ii和i+1i+1,则环形序列中必定有满足条件a[i]\leq T\;\;a[i+1]\geq Ta[i]≤Ta[i+1]≥T的两个相邻位置,这样a[i],\;a[i+1]a[i],a[i+1]之间没有交换,a[i]\leq Ta[i]≤T可以从a[i-1]a[i−1]获得纸牌,a[i+1]\geq Ta[i+1]≥T可以把多的纸牌给a[i+2]a[i+2])。
于是由上面的性质,我们直接破环成链,枚举相邻的不需要交换纸牌的两人(将其分别放在第一和最后一个位置)。
按开始的序列顺序,像普通均分纸牌一样处理出ss数组,那么假设枚举的位置为kk,则类比普通均分纸牌求法,新的s[i]=s[i]-s[k]s[i]=s[i]−s[k](注意ss为前缀和),于是ans=\sum{|s[i]-s[k]|}ans=∑∣s[i]−s[k]∣,我们套用中学数学知识可知当s[k]s[k]为ss中位数时,ansans最小。于是本题就解决了。
#include<bits/stdc++.h>
#define il inline
#define ll long long
using namespace std;
const int N=105;
ll n,a[N],sum,s[N];
int main()
{
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
sum/=n;
for(int i=1;i<=n;i++)a[i]-=sum,s[i]=s[i-1]+a[i];
sort(s+1,s+n+1);
sum=0;
for(int i=1;i<=n;i++)sum+=abs(s[n/2+1]-s[i]);
cout<<sum;
return 0;
}
View Code