一类物品均分传递问题的汇总
第一题:均分纸牌
戳我进均分纸牌
题意: n n n堆纸牌,每堆有 a i a_i ai个,每堆可以向其左右两堆传递任意数量的纸牌,如第 i i i对可以向第 i − 1 i-1 i−1和第 i + 1 i+1 i+1堆传递任意不超过该堆数量的纸牌,特别地,第 1 1 1堆只可以向第 2 2 2堆传递,第 n n n堆只可以向第 n − 1 n-1 n−1堆传递。问使得所有堆的纸牌数量都相等所需的最少移动次数。
数据范围: 1 ≤ n ≤ 100 , 1 ≤ a i ≤ 10000 1\leq n\leq 100, 1\leq a_i\leq 10000 1≤n≤100,1≤ai≤10000
题解:
本题中虽然是中间部分仍然可以向两边传递,但是由于是任意数量的传递且该题没有构成环状,所以更为简单。
由于每堆必须达到平均值,从第 1 1 1堆开始,如果 a 1 < a v e r a g e a_1<average a1<average,则只能从 a 2 a_2 a2来获得纸牌,如果 a 1 > a v e r a g e a_1>average a1>average,则只能向 a 2 a_2 a2传递多余的纸牌,考虑 a 2 a_2 a2要达到平均值时, a 1 a_1 a1已经达到平均值并且不能从其他堆获得或传递纸牌,所以它只能从 a 3 a_3 a3获取或向 a 3 a_3 a3传递纸牌,依次类推,每堆只和其后面的一堆发生传递关系,因此只需要 O ( n ) O(n) O(n)遍历即可。
代码:
#include<cstdio>
const int N = 1e5 + 10;
int a[N], n;
int res = 0;
int ave = 0;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]), ave += a[i];
ave /= n;
for(int i = 1; i <= n; ++i)
if(a[i] != ave) a[i + 1] += a[i] - ave, ++res;
printf("%d\n", res);
return 0;
}
第二题:糖果传递
戳我进糖果传递
题意: 有 n n n个小朋友,围起来坐成一圈,初始手上有 a i a_i ai个糖果,问是否可以通过一种传递方式使得每个小朋友最后手中的糖果数量都一样。每个小朋友只会和他左边或右边的小朋友传递糖果,如果可以使得最终每个小朋友都拥有相同的糖果数,再求出最少传递次数,一次传递只可以传递一个糖果。
数据范围: 1 ≤ n ≤ 1 0 6 1\leq n\leq 10^6 1≤n≤106
题解:
由于我们并不知道所有人传递和被传递的糖果数,那么我们可以假设每个人传递了 x i x_i xi个糖果,这里表示 x i x_i xi表示第 i i i个小朋友传递给第 i − 1 i-1 i−1个小朋友的糖果数,特别地,当 i i i为 1 1 1时, x 1 x_1 x1表示第 1 1 1个小朋友传递给第 n n n个小朋友的糖果数。
使得所有小朋友拥有相同糖果数的条件是: ∑ i = 1 n a i m o d n = 0 \sum_{i=1}^n a_i \mod n=0 ∑i=1naimodn=0
这里设 b = ∑ i = 1 n a i n b=\frac{\sum_{i=1}^n a_i}{n} b=n∑i=1nai
那么有如下 n n n个方程:
{ b = a 1 − x 1 + x 2 b = a 2 − x 2 + x 3 b = a 3 − x 3 + x 4 . . . . . . b = a n − 2 − x x − 2 + x n − 1 b = a n − 1 − x n − 1 + x n b = a n − x n + x 1 \left\{\begin{array}{l} b=a_1-x_1+x_2\\ b=a_2-x_2+x_3\\ b=a_3-x_3+x_4\\ ......\\ b=a_{n-2}-x_{x-2}+x_{n-1}\\ b=a_{n-1}-x_{n-1}+x_n\\ b=a_{n}-x_n+x_1\\ \end{array}\right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧b=a1−x1+x2b=a2−x2+x3b=a3−x3+x4......b=an−2−xx−2+xn−1b=an−1−xn−1+xnb=an−xn+x1
其中完全独立的方程只有 n − 1 n-1 n−1个, n n n个未知数需要有 n n n个完全独立的方程才能解出所有值,所以此时并不能解出该方程的值。由于我们要求的是 ∣ x 1 ∣ + ∣ x 2 ∣ + . . . + ∣ x n − 1 ∣ + ∣ x n ∣ |x_1|+|x_2|+...+|x_{n-1}|+|x_n| ∣x1∣+∣x2∣+...+∣xn−1∣+∣xn∣最小值,那么我们以 x 1 x_1 x1作为表示项.
先移项,并且添加一项 x 1 = x 1 − 0 x_1=x_1-0 x1=x1−0:
{ x 1 = x 1 − 0 x 2 = x 1 − ( a 1 − b ) = x 1 − ( a 1 − b ) x 3 = x 2 − ( a 2 − b ) = x 1 − ( a 1 + a 2 − 2 b ) x 4 = x 3 − ( a 3 − b ) = x 1 − ( a 1 + a 2 + a 3 − 3 b ) . . . . . . x n − 1 = x n − 2 − ( a n − 2 − b ) = x 1 − ( a 1 + a 2 + . . . + a n − 3 + a n − 2 − ( n − 2 ) b ) x n = x n − 1 − ( a n − 1 − b ) = x 1 − ( a 1 + . . . + a n − 1 − ( n − 1 ) b ) \left\{\begin{array}{l} x_1=x_1-0\\ x_2=x_1-(a_1-b)=x_1-(a_1-b)\\ x_3=x_2-(a_2-b)=x_1-(a_1+a_2-2b)\\ x_4=x_3-(a_3-b)=x_1-(a_1+a_2+a_3-3b)\\ ......\\ x_{n-1}=x_{n-2}-(a_{n-2}-b)=x_1-(a_1+a_2+...+a_{n-3}+a_{n-2}-(n-2)b)\\ x_{n}=x_{n-1}-(a_{n-1}-b)=x_1-(a_1+...+a_{n-1}-(n-1)b) \end{array}\right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧x1=x1−0x2=x1−(a1−b)=x1−(a1−b)x3=x2−(a2−b)=x1−(a1+a2−2b)x4=x3−(a3−b)=x1−(a1+a2+a3−3b)......xn−1=xn−2−(an−2−b)=x1−(a1+a2+...+an−3+an−2−(n−2)b)xn=xn−1−(an−1−b)=x1−(a1+...+an−1−(n−1)b)
那么所要求的 ∣ x 1 ∣ + ∣ x 2 ∣ + . . . + ∣ x n − 1 ∣ + ∣ x n ∣ |x_1|+|x_2|+...+|x_{n-1}|+|x_n| ∣x1∣+∣x2∣+...+∣xn−1∣+∣xn∣最小值可以转换为一维坐标轴上 n n n个点到 x 1 x_1 x1的距离之和。
那么本题就转换为了经典问题了:
- 解法一:找到中位数,求每个点到中位数的距离之和
- 解法二:求 1 1 1与 n n n, 2 2 2与 n − 1 , . . . , n-1,...\ , n−1,... , n 2 \frac{n}{2} 2n与 n 2 + 1 \frac{n}{2}+1 2n+1,如果 n n n为奇数数则最后一项为 n 2 − 1 \frac{n}{2}-1 2n−1与 n 2 + 1 \frac{n}{2}+1 2n+1的这 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor ⌊2n⌋组数对的距离之和。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int a[N], n;
ll p[N];
ll pre[N], ave;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]), pre[i] = pre[i - 1] + a[i];
ave = pre[n] / n;
p[1] = 0;
for(int i = 2; i <= n; ++i)
p[i] = pre[i - 1] - 1ll * (i - 1) * ave;
sort(p + 1, p + n + 1);
ll res = 0;
for(int i = 1; i <= (n >> 1); ++i) res += p[n - i + 1] - p[i];
printf("%lld\n", res);
return 0;
}
求取无序序列的中位数的 O ( n ) O(n) O(n)trick:
- 通过快排每次选取一半
- 直接调用 O ( n ) O(n) O(n)的 n t h _ e l e m e n t nth\_element nth_element函数
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int a[N], n;
ll p[N];
ll pre[N], ave;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]), pre[i] = pre[i - 1] + a[i];
ave = pre[n] / n;
p[1] = 0;
for(int i = 2; i <= n; ++i)
p[i] = pre[i - 1] - 1ll * (i - 1) * ave;
nth_element(p + 1, p + (n + 1) / 2, p + n + 1);
ll mid = p[(n + 1) / 2];
ll res = 0;
for(int i = 1; i <= n; ++i) res += abs(p[i] - mid);
printf("%lld\n", res);
return 0;
}
第三题:七夕祭
戳我进七夕祭
题意:
n n n行 m m m列的摊点中,有 k k k个是七夕节有关的,现在让你移动一些摊点使得 n n n行 m m m列中每一行中与七夕有关的摊点数都相同,每一列中与七夕有关的摊点数也都相同,一个摊点一次只可以被移动到上下左右四个位置之一,现在问使得每一行中与七夕有关的摊点数一样,以及使得每一列中与七夕有关的摊点也一样的最少移动次数。
数据范围: 1 ≤ n , m ≤ 1 0 5 , 0 ≤ k ≤ m i n ( n × m , 1 0 5 ) 1\leq\ n,m\leq 10^5,0\leq k\leq min(n\times m, 10^5) 1≤ n,m≤105,0≤k≤min(n×m,105)
题解:
考虑到移动摊点使得每一行与七夕有关的摊点数都相同的操作,只会选择一列,在这一列上进行上下的移动,并不会改变该列中与七夕有关的摊点数数量。同样的,移动摊点使得每一列与七夕有关的摊点数都相同的操作,只会选择一行,在这一行上进行左右的移动,并不会改变该行中与七夕有关的摊点数数量,因此对行列的操作是独立的。
所以我们只需要单独考虑这两部分即可。细节同第二题。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, m, k;
int x[N], y[N];
ll pre[N], p[N];
ll get(int n, int a[]) {
ll ans = 0;
int ave = k / n;
for(int i = 1; i <= n; ++i) pre[i] = pre[i - 1] + a[i];
p[1] = 0;
for(int i = 1; i <= n; ++i)
p[i] = pre[i - 1] + 1ll * (i - 1) * ave;
sort(p + 1, p + n + 1);
for(int i = 1; i <= (n >> 1); ++i) ans += p[n - i + 1] - p[i];
return ans;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++i) {
int a, b;
scanf("%d%d", &a, &b);
++x[a], ++y[b];
}
int r = k % n, c = k % m;
ll res = 0;
if(!r) res += get(n, x);
if(!c) res += get(m, y);
if(!r && !c) printf("both %lld\n", res);
else if(!r) printf("row %lld\n", res);
else if(!c) printf("column %lld\n", res);
else printf("impossible");
}