七夕祭

题目link:https://www.acwing.com/problem/content/107/

首先考虑将这题化简一下。容易得出交换两行和交换两列的操作是不会互相影响的,即交换了两列之后,每一行的摊位数量不变,交换行同理。

因此可以对于行进行操作,再对列进行操作(两者做法相同)。

先不管列,就考虑行,如果将每一行看成一个人,每一行里的感兴趣的摊的数量看成这个人手中的糖果的话,那么此时交换两行的操作就变成了相邻的两个人可以互相给糖果(这几个人围成一圈),代价为给的糖果的数量,求的内容变成求在代价最小的情况下使得每一个人手中糖果数量一致(求最小代价)。为什么是糖果?详见 Link (糖果传递)。

此时相当于做出 糖果传递 ,这题也就迎刃而解了。考虑糖果传递做法。

首先对于糖果这题,题目保证存在一组解;但是七夕祭这题却有无解的情况,因此需要提前特判一下,处理方法只需判断糖果数量是否为人的倍数。

然后再来考虑做法,此题是一个环,环上处理问题一般比在序列中处理困难得多。于是考虑将环转化为一个序列。

环转化为序列无异于找到一条边将它断开,但是此题是否一定有一组最优解使得存在两个人之间没有传递糖果?答案是肯定的,考虑证明。

假设此时一共有 $n$ 个人,并且已经得到了一组最优解为第一个人传递给了第二个人 $X1$ 个糖果,第二个人传递给了第三个人 $X2$ 个糖果,……,第 $n$ 个人传递给了第一个人 $Xn$ 个糖果( $Xi$ 为若为负则为反过来传 $|Xi|$ 个糖果,为 $0$ 则不传)。

此时这个最优解的答案为 $∑$ $|Xi|$ 。

假设存在另一组最优解使得第 $n$ 个人没有给第一个人传递糖果,那么相当于第一个人少得到了 $Xn$ 个糖果,于是他就得少给第二个人 $Xn$ 个糖果来保证他此时糖果数量仍为符合条件(即为初始每个人糖果数的 $ave$),同理第二个人又少得到了 $Xn$ 个糖果,于是他需要少给第三个人 $Xn$ 个糖果来保证他此时糖果数仍为符合条件,以此类推,第 $n$ $-$ $1$ 个人就得少给第 $n$ 个人 $Xn$ 个糖果来保证他此时糖果数量6仍为符合条件。

此时这个最优解的答案为 $∑$ $|Xi$ $-$ $Xn|$ 。

那么现在转化为证明存在一组 $X1$ , $X2$ , $X3$ ,……, $Xn$ 和 $0$ 的大小关系(大于或小于关系即可),使得 $∑$ $|Xi|$ $=$ $∑$ $|Xi$ $-$ $Xn|$ 。

随便举个例子(给出 $n$ 即可)扔进程序里算就很容易得出一组解(雾),证毕。

现在得出了 “一定有一组最优解使得存在两个人之间没有传递糖果“ 这个结论,那么就可以考虑断哪两个点之间的边答案最优了。

既然已经转化为了序列上的问题,那么可以考虑一下这个序列问题的答案表达式是多少(可以参考 均分纸牌:Link(序列版的糖果传递,但是注意它这个里面要的答案是最少移动次数,而我们要的表达式是最少移动的纸牌数))。

这个 均分纸牌 可以先考虑第一个牌堆,它只能通过第二个牌堆移牌过来(或把牌移到第二个牌堆)。当它的牌堆数满足条件时(即为原始牌堆数的 $ave$ ),它就没有用了,于是可以将第二个牌堆看做第一个,以此类推。

设一共有 $n$ 堆牌,第 $i$ 个牌堆的牌数为 $Ai$ ,所有牌堆数的平均数为 $a$ , $S$ 数组为 $A$ 数组的前缀和(即 $Si$ $=$ $∑$ $Aj$ $(1$ $<=$ $j$ $<=$ $i)$),则容易得出答案表达式为 $∑$ $|Si$ $-$ $a$ $*$ $i|$ 。

此时再回到糖果传递这题,假设将第 $i$ 个人和第 $i$ $+$ $1$ 个人之间的边断开,考虑通过 $S$ 数组和 $a$ 来表示此时的答案表达式,仿照均分纸牌,来与 均分纸牌 的表达式进行比较。可以得出它是一个非常复杂的一个式子。

设 $Ti$ 表示 $Si$ $-$ $a$ $*$ $i$ 则那个复杂的式子便化简为 $∑$ $|Ti$ $-$ $Tj|$ 。而按照题意,希望答案最小,则是想要这个式子最小。

看到这个式子,还是求最小值,很容易想到它是一个 货仓选址 问题:Link,最优方案下 $Ti$ 为所有 $Tj$ 的中位数,然后直接计算即可(证明略)。

$code:$

 1 #include <bits/stdc++.h>
 2 #define INF 0x3f3f3f3f
 3 #define ll long long
 4 using namespace std;
 5 const int Maxn = 1e5 + 10;
 6 int n, m, t, A1[Maxn], A2[Maxn];
 7 ll T1[Maxn], T2[Maxn], S1[Maxn], S2[Maxn], ans1, ans2, a1, a2;
 8 void solve1()
 9 {
10     a1 = S1[n] / n;
11     for(int i = 1; i <= n; ++i) T1[i] = S1[i] - a1 * i;
12     int mid = (n + 1) >> 1;
13     nth_element(T1 + 1, T1 + mid, T1 + n + 1);
14     for(int i = 1; i <= n; ++i) ans1 += abs(T1[mid] - T1[i]);
15     return;
16 }
17 void solve2()
18 {
19     a2 = S2[m] / m;
20     for(int i = 1; i <= m; ++i) T2[i] = S2[i] - a2 * i;
21     int mid = (m + 1) >> 1;
22     nth_element(T2 + 1, T2 + mid, T2 + m + 1);
23     for(int i = 1; i <= m; ++i) ans2 += abs(T2[mid] - T2[i]);
24     return;
25 }
26 int main()
27 {
28     scanf("%d %d %d", &n, &m, &t); for(int i = 1, x, y; i <= t; ++i) scanf("%d %d", &x, &y), ++A1[x], ++A2[y];
29     for(int i = 1; i <= n; ++i) S1[i] = S1[i - 1] + A1[i];
30     for(int i = 1; i <= m; ++i) S2[i] = S2[i - 1] + A2[i];
31     if(S1[n] % n != 0 && S2[m] % m != 0) puts("impossible");
32     else if(S1[n] % n == 0 && S2[m] % m != 0) solve1(), printf("row %lld", ans1);
33     else if(S1[n] % n != 0 && S2[m] % m == 0) solve2(), printf("column %lld", ans2);
34     else solve1(), solve2(), printf("both %lld", ans1 + ans2);
35     return 0;
36 } 

 

posted @ 2021-01-25 17:46  louis_11  阅读(108)  评论(0编辑  收藏  举报