P4016 负载平衡问题 最小费用最大流 or 贪心

原文链接: http://www.cnblogs.com/bxd123/p/10927335.html

  

题目描述

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]=Ta[i],则让前kk个人牌数相同需要的交换牌数为\sum\limits_{i=1}^{i\leq k}{|s[i]|}i=1iks[i]∣,其中s[i]=\sum\limits_{j=1}^{j\leq i}{g[i]}s[i]=j=1jig[i],可以这样理解,要让前kk个人牌数相同,要依次让前1,2,3…k-11,2,3k1个人牌数相同,多退少补,会与后边的人发生二者之差绝对值的牌数交换。所以移动总牌数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[i1]获得纸牌,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












转载于:https://www.cnblogs.com/bxd123/p/10927335.html

猜你喜欢

转载自blog.csdn.net/weixin_30338497/article/details/95028156
今日推荐