题目描述
GG 公司有 nn 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 nn 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。
输入输出格式
输入格式:
文件的第 11 行中有 11 个正整数 nn,表示有 nn 个仓库。
第 22 行中有 nn 个正整数,表示 nn 个仓库的库存量。
输出格式:
输出最少搬运量。
输入输出样例
5 17 9 14 16 4
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; }
贪心:转自洛谷巨佬 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个人牌数相同需要的交换牌数为sumlimits_{i=1}^{ileq k}{|s[i]|}i=1∑i≤k∣s[i]∣,其中s[i]=sumlimits_{j=1}^{jleq 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; }