zoukankan      html  css  js  c++  java
  • 关于纸牌均分的解法及扩展

    一、纸牌均分

      给定n堆纸牌,每堆纸牌有若干张,现要使着n堆纸牌平均分配,即每堆张数相等。每次移动可以使一堆牌向其左边的一堆或右边的一堆移动若干张牌。求最少移动次数。

      这是我们最熟悉的纸牌均分问题。正确的解法是贪心。我们可以知道均分后每堆纸牌的张数,将现在的牌数减去,就可以得到差值b[i],表示第i堆牌比目标牌数多几张或少几张(b[i]有正有负)。然后我们从第一堆开始往后做,我们把第i堆多出来的b[i]张牌直接移到i + 1上,b[i + 1] += b[i],然后移动数bns加一,如果b[i]为0则不用。注意,此处的关键在于,无论b[i]为正为负都会往右移,即使是负数也没有关系,因为我们这里指的不是真正的移牌,而是指第i堆与目标牌数的差异b[i]由i + 1堆来承担,多了移给i + 1,少了从i + 1移过来,i + 1不够了再从i + 2移过来,以此类推。

      这个贪心的正确性还是比较明显的。

      题目见noip2002提高组。

      

    {
        for (int i = 1;i <= n;i ++)
            b[i] = a[i] - t / n;
        for (int i = 1;i <= n;i ++)
        {
             if (b[i] != 0) ans ++;
             b[i + 1] += b[i];
        }      
    }

    二、每次只能移动一张牌的纸牌均分

      和上道题差不多,只不过这回一次只能移动1张牌。

      这题虽然每次只能移动一张,但是其实贪心的方法是一样的,只不过是移动次数的统计不一样而已。我们将刚才求得的b数组求前缀和sum[i],sum[i] = ∑b[j] (j <= i)。sum[i]其实表示的就是i这堆总共要向左边移动的牌数(如果小于0则表示要向右移动的牌),那么移动总次数就是∑sum[i]

    {
        for (int i = 1;i <= n;i ++)
            b[i] = a[i] - t / n;
        for (int i = 1;i <= n;i ++)
        {
             sum[i] = sum[i - 1] + b[i];
             ans += abs(sum[i]);    
        }      
    }

    三、每次只能移动一张牌的环状纸牌均分

      这个扩展比上面一个就是多了个环状。环状,即首尾相连,第一堆可以向最后一堆移牌,最后一堆可以向第一堆移牌。看似区别不大,但算法还是需要进一步改进。

      正常的想法,直接枚举断点,将环断成一条链,然后就是和二一样的做法了。但是这样的话复杂度就升高到了O(N^2),要过大数据有点难,我们还是期望时间复杂度能够尽量小一点。

      如果我们在1断开,那么就是一条链,和二一样求前缀和sum求和就可以了。假设我们在i - 1,i之间断开,那么就是以i为起点重新排成一条链,然后重新计算前缀和sum。但是其实并不需要重新计算前缀和,对于一个点j,它新的前缀和即是i到j的所有点的b值加起来,以我们之前算的从1开始的前缀和来算的话,

      sum'[j] = sum[j] - sum[i - 1]。

      又ans = ∑abs(sum'[j]),

      即ans = ∑abs(sum[j] - sum[i - 1]),(其中i为断点)。

      要使ans最小,很容易想到的就是sum[i - 1]取所有数的中位数,这样便能保持ans的最小。这样的话我们的做法就很简单了,把sum数组排序一遍,然后找到中位数,按照上式就能模拟出答案了。  

      tyvj1924(Poetize 杯NOIP模拟赛 II 七夕祭 )也就是这个模型。

      

    {
        for (int i = 1;i <= n;i ++)
            b[i] = a[i] - t / n;
        for (int i = 1;i <= n;i ++)
            sum[i] = sum[i - 1] + b[i];
        sort(sum + 1,sum + n + 1);
        int ans = 0;
        for (int i = 1;i <= n;i ++)
            ans += abs(sum[i] - sum[(1 + n) / 2]);
    }

    注:代码仅供参考

  • 相关阅读:
    简单工厂笔记
    P3369 【模板】普通平衡树 Treap树堆学习笔记
    tp5阿里云短信验证码
    centos 安装php
    tp6.0.2开启多应用模式
    linux navicat最新版过期
    git commit之后 取消commit
    服务器重置之后ssh root@报错
    git pull push 每次都需要输入账号和密码
    跨域问题 php
  • 原文地址:https://www.cnblogs.com/N-C-Derek/p/3382458.html
Copyright © 2011-2022 走看看