ACW105
参考了秦淮岸大佬的答案,自己的一点小心得
引子
在写这个题之前先引入一个简单版的题目:环形均分纸牌问题
有n个小朋友坐成一圈,每人有ai个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为1。
为了简化问题,我们假设是最后能够成功均分的.这样每个人最后能拿到sum/n张纸牌.我们定义数组p[]
, p[i]
表示i
给i+1
多少张纸牌,p[n]
表示是n号给1号多少张纸牌
我们发现,
- 对于
i>1
,都有p[i] = a[i] + p[i-1] -sum/n
将其写成
p[i] - p[i-1] = a[i] -sum/n
- 对于 i==1,有 p[1] -p[n] = a[1] -sum/n;
两个式子递归代入之后,我们就得到了,对于i>=1,有
p[i]=i∑i=1(a[i]−sum/n)+p[n]
我们令右式等于c[i]+p[n]
我们最后要求的结果是
ans=n∑i=1|p[i]|=n∑i=1|c[i]+p[n]|=n∑i=1|c[i]−(−p[n])|
根据ACW104(本人也写过此题题解)的结论,(-p[n])等于c[]
数组的中位数时,此式取得最小值
AC代码
// 题外话,环形均分纸牌/糖果
// 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
输入样例:
2 3 4
1 3
2 1
2 2
2 3
输出样例:
row 1
思路
这个题猛一看似乎就是二维的糖果传递问题.但是我们要论证一下在每个维度上是可以进行这种操作的.
其实只要证明一种特殊情况:a[i]==a[i+1]且p[i]不为零
这种情况是有解的即可.(在这种情况下可能会出现a[i]和a[i+1]上的点一一对应,导致无法交换).
- 如果p[i-1]不为零,则可以先通过p[i-1]的操作使得a[i]不等于a[i+1],同理p[i+1]也是
- 如果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代码
// 非常经典与重要的例题
// 环形均分纸牌问题(贪心)
#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;
}
大佬,其实不止这一种情况,还有a[i]==a[i]+x且p[i]不为0,但是无法交换全部p[i]。只不过反证也能证出不对。您觉得呢?