「CodeNote」 ACW105(均分纸牌+思维)

Posted by Dawn-K's Blog on August 6, 2019

ACW105

引子

在写这个题之前先引入一个简单版的题目:环形均分纸牌问题

有n个小朋友坐成一圈,每人有ai个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为1。

糖果传递

为了简化问题,我们假设是最后能够成功均分的.这样每个人最后能拿到sum/n张纸牌.我们定义数组p[] , p[i] 表示ii+1 多少张纸牌,p[n]表示是n号给1号多少张纸牌

我们发现,

  1. 对于i>1 ,都有 p[i] = a[i] + p[i-1] -sum/n

    将其写成

    p[i] - p[i-1] = a[i] -sum/n

  2. 对于 i==1,有 p[1] -p[n] = a[1] -sum/n;

两个式子递归代入之后,我们就得到了,对于i>=1,有 \(p[i]=\sum_{i=1}^i (a[i]-sum/n) + p[n]\) 我们令右式等于c[i]+p[n]

我们最后要求的结果是 \(ans=\sum_{i=1}^n |p[i]| = \sum_{i=1}^n |c[i]+p[n]|=\sum_{i=1}^n |c[i]-(-p[n])|\) 根据ACW104(本人也写过此题题解)的结论,(-p[n])等于c[]数组的中位数时,此式取得最小值

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 题外话,环形均分纸牌/糖果
// P2512 [HAOI2008]糖果传递
// 洛谷
// 前缀和 + 数学 + 思维
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>

#define ll long long
#define LL ll
using namespace std;
int a[1001000];
int d[1001000];
int p[1001000];

int main() {
    int n;
    while (cin >> n) {
        ll sum = 0;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
            sum += a[i];
        }
        int avg = sum / n; // 求平均数
        d[0] = 0; // 其实这个d数组就是前文的c数组
        for (int i = 1; i <= n; ++i) {
            d[i] = d[i - 1] + a[i] - avg;
        }
        sort(d + 1, d + 1 + n);
        ll ans = 0;
        for (int i = 1; i <= n; ++i) {
            ans += abs(d[i] - d[(n + 1) / 2]);
        }
        cout << ans << endl;
    }
    return 0;
}

题意

七夕节因牛郎织女的传说而被扣上了「情人节」的帽子。

于是TYVJ今年举办了一次线下七夕祭。

Vani同学今年成功邀请到了cl同学陪他来共度七夕,于是他们决定去TYVJ七夕祭游玩。

TYVJ七夕祭和11区的夏祭的形式很像。

矩形的祭典会场由N排M列共计N×M个摊点组成。

虽然摊点种类繁多,不过cl只对其中的一部分摊点感兴趣,比如章鱼烧、苹果糖、棉花糖、射的屋……什么的。

Vani预先联系了七夕祭的负责人zhq,希望能够通过恰当地布置会场,使得各行中cl感兴趣的摊点数一样多,并且各列中cl感兴趣的摊点数也一样多。

不过zhq告诉Vani,摊点已经随意布置完毕了,如果想满足cl的要求,唯一的调整方式就是交换两个相邻的摊点。

两个摊点相邻,当且仅当他们处在同一行或者同一列的相邻位置上。

由于zhq率领的TYVJ开发小组成功地扭曲了空间,每一行或每一列的第一个位置和最后一个位置也算作相邻。

现在Vani想知道他的两个要求最多能满足多少个。

在此前提下,至少需要交换多少次摊点。

输入格式

第一行包含三个整数N和M和T,T表示cl对多少个摊点感兴趣。

接下来T行,每行两个整数x, y,表示cl对处在第x行第y列的摊点感兴趣。

输出格式

首先输出一个字符串。

如果能满足Vani的全部两个要求,输出both;

如果通过调整只能使得各行中cl感兴趣的摊点数一样多,输出row;

如果只能使各列中cl感兴趣的摊点数一样多,输出column;

如果均不能满足,输出impossible。

如果输出的字符串不是impossible, 接下来输出最小交换次数,与字符串之间用一个空格隔开。

数据范围

1≤N,M≤100000 0≤T≤min(N∗M,100000) 1≤x≤N, 1≤y≤M

输入样例:

1
2
3
4
5
2 3 4
1 3
2 1
2 2
2 3

输出样例:

1
row 1

思路

这个题猛一看似乎就是二维的糖果传递问题.但是我们要论证一下在每个维度上是可以进行这种操作的.

其实只要证明一种特殊情况:a[i]==a[i+1]且p[i]不为零 这种情况是有解的即可.(在这种情况下可能会出现a[i]和a[i+1]上的点一一对应,导致无法交换).

  1. 如果p[i-1]不为零,则可以先通过p[i-1]的操作使得a[i]不等于a[i+1],同理p[i+1]也是
  2. 如果p[i-1]和p[i+1]同时为零,实际上这种情况是不存在的,因为如果进行p[i]这种操作之后,a[i]就不会和a[i+1]相等了,而p[i-1]和p[i+1]都为零的操作使得a[i]和a[i+1]的值不会再被其他的元素改变,所以最后不可能到达全部相同的情况的.

综上所述,两行之间一定能发生交换,可以直接降维到一维来解决,同时两维是独立的

我们可以将引子的思路封装成一个函数,然后在横纵两个方向解决此问题

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 非常经典与重要的例题
// 环形均分纸牌问题(贪心)
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <string>
#include <unordered_map>

#define  ll long long
#define LL ll
using namespace std;
int n, m, T;
int d[100100];
int row[100100];
int col[100100];

ll work(const int b[], int num) {
    ll sum = 0;
    for (int i = 1; i <= num; ++i) {
        sum += b[i];
    }
    int avg = sum / num; // 求平均数
    d[0] = 0;
    for (int i = 1; i <= num; ++i) {
        d[i] = d[i - 1] + b[i] - avg;
    }
    sort(d + 1, d + 1 + num);
    ll ans = 0;
    for (int i = 1; i <= num; ++i) {
        ans += abs(d[i] - d[(num + 1) / 2]);
    }
    return ans;
}

int main() {
    while (cin >> n >> m >> T) {
        int x, y;
        memset(row, 0, sizeof(row));
        memset(col, 0, sizeof(col));
        for (int i = 0; i < T; ++i) {
            cin >> x >> y;
            ++row[x];
            ++col[y];
        }

        if (T % n && T % m) {
            cout << "impossible" << endl;
            continue;
        }

        if (T % n == 0 && T % m) {
            cout << "row ";
            cout << work(row, n) << endl;
        } else if (T % n && T % m == 0) {
            cout << "column ";
            cout << work(col, m) << endl;
        } else if (T % n == 0 && T % m == 0) {
            cout << "both "; // 两个维度彼此独立,因此可以直接相加
            cout << work(row, n) + work(col, m) << endl;
        }
    }
    return 0;
}