【算法专题】均分纸牌问题

均分纸牌问题

1. 概述

  • n个小朋友,每个小朋友手中有一些糖果,每个小朋友都可以把他手中的糖果给相邻的小朋友,问使得所有小朋友手中糖果数量相同需要多少至少给多少次?或者至少传递多少糖果?

  • 存在两种类型:

    • 均分纸牌问题,即小朋友没有形成一个环,第一个小朋友只能给右侧小朋友,最右侧小朋友只能给左侧小朋友,对应例题:AcWing 1536. 均分纸牌

    • 环形均分纸牌问题,小朋友形成一个环,每个小朋友都可以向左右小朋友给糖果,对应例题:AcWing 122. 糖果传递AcWing 105. 七夕祭

2. 例题

AcWing 1536. 均分纸牌

问题描述

分析

  • 如下图,按下图方向所示进行操作,如果x是负数,表示反向给糖果。

在这里插入图片描述

  • 因为最终每个小朋友的糖果数目相同,因此每个小朋友的糖果数目应该为平均值,即:
    a = a 1 + a 2 + . . . + a n n a = \frac{a_1 + a_2 + ... + a _n}{n} a=na1+a2+...+an

  • 因此:

{ a 1 + x 2 = a a 2 − x 2 + x 3 = a . . . . . . a n − 1 − x n − 1 + x n = a a n − x n = a \begin{cases} a_1 + x_2 = a \\ a_2 - x_2 + x_3 = a \\ ...... \\ a_{n-1} - x_{n-1} + x_n = a \\ a_n - x_n = a \end{cases} a1+x2=aa2x2+x3=a......an1xn1+xn=aanxn=a

  • 可以发现,我们用前n-1个等式就可以求出 x 2 、 x 3 、 . . . 、 x n x_2、x_3、...、x_n x2x3...xn ,如下:

{ x 2 = a − a 1 x 3 = a − a 2 + x 2 . . . . . . x n = a − a n − 1 + x n − 1 \begin{cases} x_2 = a - a_1 \\ x_3 = a - a_2 + x_2 \\ ...... \\ x_n = a - a_{n-1} + x_{n-1} \\ \end{cases} x2=aa1x3=aa2+x2......xn=aan1+xn1

  • 可以发现使用递推就可以求出这些x值。

  • 非零的x值表示需要操作一次,因此统计有多少非零值,答案就是多少。

  • 还需要考虑一个问题,就是能否取到这个结果,就是说能否构造出这样的方案。

  • 这里类似数学归纳法,当只有两堆的话结论成立,当右侧n-1堆存在一种转移方案,则考虑加上左边一堆 a 1 a_1 a1 是否仍然能够实现。

  • 如果此时 x 2 = = 0 x_2 == 0 x2==0 ,说明不用转移,结论成立;如果 x 2 < 0 x_2 < 0 x2<0,说明 a 1 = a + ∣ x 1 ∣ a_1 = a+ |x_1| a1=a+x1 a 1 a_1 a1 向右侧转移 x 2 x_2 x2 数量的牌,问题就变为了n-1的子问题,由归纳假设,结论成立;如果 x 2 > 0 x_2 > 0 x2>0,可以先操作右侧n-1堆,会使得 a 3 a_3 a3 ~ a n a_n an 都变为 a a 2 a_2 a2 变为 a + x 2 a + x_2 a+x2,此时 a 2 a_2 a2 a 1 a_1 a1 转移 x 2 x_2 x2 数量的牌即可,成立。

代码

  • C++
#include <iostream>

using namespace std;

const int N = 110;

int n;
int a[N];

int main() {
    
    cin >> n;
    int sum = 0;
    for (int i = 1; i <= n; i++) cin >> a[i], sum += a[i];
    
    int res = 0;  // 需要操作的次数
    int avg = sum / n;
    for (int i = 1, x = 0; i <= n; i++) {
        x = avg - a[i] + x;
        if (x) res++;
    }
    
    cout << res << endl;
    
    return 0;
}

AcWing 122. 糖果传递

问题描述

分析

  • 如下图,按下图方向所示进行操作,如果x是负数,表示反向给糖果。我们的目标是求当a1=a2=...=a2|x1|+|x2|+...+|xn|的最小值。

在这里插入图片描述

  • 因为最终每个小朋友的糖果数目相同,因此每个小朋友的糖果数目应该为平均值,即:

    a = a 1 + a 2 + . . . + a n n a = \frac{a_1 + a_2 + ... + a _n}{n} a=na1+a2+...+an

  • 因此:

{ a 1 − x 1 + x 2 = a a 2 − x 2 + x 3 = a . . . . . . a n − 1 − x n − 1 + x n = a a n − x n + x 1 = a \begin{cases} a_1 - x_1 + x_2 = a \\ a_2 - x_2 + x_3 = a \\ ...... \\ a_{n-1} - x_{n-1} + x_n = a \\ a_n - x_n + x_1 = a \end{cases} a1x1+x2=aa2x2+x3=a......an1xn1+xn=aanxn+x1=a

  • 整理得到:

{ x 1 − x 2 = a 1 − a x 2 − x 3 = a 2 − a . . . . . . x n − 1 − x n = a n − 1 − a x n − x 1 = a n − a \begin{cases} x_1 - x_2 = a_1 - a \\ x_2 - x_3 = a_2 - a \\ ...... \\ x_{n-1} - x_n = a_{n-1} - a \\ x_n - x_1 = a_n - a \end{cases} x1x2=a1ax2x3=a2a......xn1xn=an1axnx1=ana

  • 再次整理得到:

{ x 1 = x 1 − 0 x 2 = x 1 − ( a 1 − a ) x 3 = x 2 − ( a 2 − a ) = x 1 − ( a 1 + a 2 − 2 a ) . . . . . . x n = x 1 − ( a 1 + a 2 + . . . + a n − 1 − ( n − 1 ) a ) \begin{cases} x_1 = x_1 - 0 \\ x_2 = x_1 - (a_1 - a) \\ x_3 = x_2 - (a_2 - a) = x_1 - (a_1 + a_2 - 2a) \\ ...... \\ x_n = x_1 - (a_1 + a_2 + ... + a_{n-1} - (n-1)a) \end{cases} x1=x10x2=x1(a1a)x3=x2(a2a)=x1(a1+a22a)......xn=x1(a1+a2+...+an1(n1)a)

  • 将上述等号右侧常数部分记为c,则上式变为:

{ x 1 = x 1 − c 1 x 2 = x 1 − c 2 x 3 = x 1 − c 3 . . . . . . x n = x 1 − c n \begin{cases} x_1 = x_1 - c_1 \\ x_2 = x_1 - c_2 \\ x_3 = x_1 - c_3 \\ ...... \\ x_n = x_1 - c_n \end{cases} x1=x1c1x2=x1c2x3=x1c3......xn=x1cn

  • 因此原问题转变为求|x1-c1|+|x2-c2|+...+|x_n-cn|的最小值,此时问题变为了AcWing 104. 货仓选址

  • 将数组c排序后,x取中位数即可取到最小值。

  • 还需要考虑是否存在可以取到最小值,即存在合法方案。

  • 这里所有的 x 不可能全是正数或者全是负数,例如全是正数,说明存在冗余操作,整体可以减去数据中的最小值。因此一定存在一个位置,对应的两侧x值:一个是正数、一个是非正数,因此可以首先将这个位置处理完毕(因为这个位置只向外给),然后去掉这个位置,此时就变成了链,即变成了AcWing 1536. 均分纸牌,按照这题的构造方式构造即可。

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1000010;

int n;
LL s[N], c[N];

int main() {
    
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%lld", &s[i]);
    for (int i = 1; i <= n; i++) s[i] += s[i - 1];
    
    LL avg = s[n] / n;
    for (int i = 2; i <= n; i++) c[i] = s[i - 1] - (i - 1) * avg;
    
    sort(c + 1, c + n + 1);
    
    LL res = 0;
    for (int i = 1; i <= n; i++) res += abs(c[i] - c[(n + 1) / 2]);
    
    printf("%lld\n", res);
    
    return 0;
}

AcWing 105. 七夕祭

题目描述:AcWing 105. 七夕祭

在这里插入图片描述

分析

  • 本题的考点:排序Vani喜欢的摊点下面记为糖果。

  • 本题中对于行列的处理是独立的,当我们让糖果在行之间交换(上下交换)时,对列没有任何影响;反过来也是成立的。因此可以行列分别单独处理。可以先操作行,然后操作列。要让操作次数最少,则让两次的操作最少即可。

  • 此时问题就变为了环形均分纸牌问题。可以参考:AcWing 122. 糖果传递

代码

  • C++
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, m, cnt;
int row[N], col[N], s[N], c[N];

LL work(int n, int a[]) {
    // 计算前缀和
    for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
    
    if (s[n] % n) return -1;  // 说明不能均分
    
    int avg = s[n] / n;
    
    c[1] = 0;
    for (int i = 2; i <= n; i++) c[i] = s[i - 1] - (i - 1) * avg;
    
    sort(c + 1, c + n + 1);
    LL res = 0;
    for (int i = 1; i <= n; i++) res += abs(c[i] - c[(n + 1) / 2]);
    
    return res;
}

int main() {
    
    scanf("%d%d%d",&n, &m, &cnt);
    while (cnt--) {
        int x, y;
        scanf("%d%d", &x, &y);
        row[x]++, col[y]++;
    }
    
    LL r = work(n, row);
    LL c = work(m, col);
    
    if (r != -1 && c != -1) printf("both %lld\n", r + c);
    else if (r != -1) printf("row %lld\n", r);
    else if (c != -1) printf("column %lld\n", c);
    else puts("impossible");
    
    return 0;
}
  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值