zoukankan      html  css  js  c++  java
  • P4016 负载平衡问题 最小费用最大流 or 贪心

      

    题目描述

    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个人牌数相同需要的交换牌数为sumlimits_{i=1}^{ileq k}{|s[i]|}i=1iks[i]∣,其中s[i]=sumlimits_{j=1}^{jleq 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












  • 相关阅读:
    Python项目生成requirements.txt的多种方式
    标准的Flask启动文件
    Flask的错误日志处理和|ORM操作
    Django的model中创建表
    Redis的删除机制、持久化 主从
    RabbitMQ 消息队列
    IP地址与子网掩码逐位相与
    IP地址转二进制
    一款很好用的工具
    放球问题
  • 原文地址:https://www.cnblogs.com/bxd123/p/10927335.html
Copyright © 2011-2022 走看看