题目链接:https://ac.nowcoder.com/acm/contest/11166/G
题目:
给你两组数,每组数有n个, 分别为a[],b[]. 现在你给你k次操作, 每次操作都可以将某个ai,aj进行交换. 进行k次操作后(必须完成k次),使得从1 到 n 的所有| a[i] - b[i] |的和最大,最后输出这个和的值.
输入:
第一行输入 n,k.
后面一行输入 n个ai;
在输入 n 个bi.
输出:
和的最大值.
思路1:
1. 使用abs()时,拆开abs则变为了一个为正值,一个为负值,也就是分配了一个正负号给两个数,如果可以交换无限次a[]的话,那么求最大值很简单。就是将a[],b[]所有的值进行排序,前半大值为正,后半大值为负。这样的情况是最优的。
但是现在有交换次数k的限制,那么就要看,它每一次的交换能够获利多少。
但在此之前需要知道最优的情况什么情况下交换呢?
+a1 -a2
+b1 -b2的情况下进行交换。
变为:
-a2 +a1
+b1 -b2的情况。
而这样的每一次交换的最优获利是2 * min(x1,x2) - 2 * max(x3 , x4)。
所以就是取min最大,max最小从而获利最大。
如果在到达k次前已经到达了获利最大的情况。
也就是min - max 接下来的选择只有了0或者负数的情况。
则我们使用0的情况。
就是
+a1 +a2
-b1 -b2
中的a1,a2不断互换,直到凑到k次即可。
分析1:
理解方式1(方式2更容易理解): 注意求abs()值时的技巧
首先,如果题目中并没有要求进行恰好k次交换,那么我们可以使用这样一种技巧, abs(a - b)实际上就是一个大值 减去 一个小值, 也就是给大值 分配 "正号" 给小值分配 "负号".所得到的结果.
那么可以进行无穷次交换的话, 首先,由于两个两个一组,进行abs操作,所以必定为一个为正,一个为负,所以分配的正号为 总数的一半, 分配的负号 也为总数的一半.
那么将所有值进行排序之后, 给前一半的大值分配正号, 给后一半的大值分配负号. 那么结果就是最大的情况.
那么问题来了? 进行无穷次交换,为什么就说明这样的操作(也就是最大的一半个数给正号,另一半小的数给负号) 能够实现呢?
证明:
假设上下两行a,b分别为n个,共2n个数,
情况1: 一半大的数全部在上面, 那么另一半小的全部在下面,无需移动.上面为正,下面为负.
情况2:一半大的数全部在下面,一半小的数全部在上面,同1,无需移动.
情况3:
上面有 m个为前一半大值, 那么就有 n - m个为后一半小的值
下面就有 n - m个前一半大的值, m个后一半小的值.
因为上下加起来必定需要满足有n个大值n个小值.
而又可以经过无穷次交换, 自然可以将上面的 n - m 个 后一半小值 交换到下面的 n-m的前一半大值的上面. 从而 使得 上面那 n - m 个为后一半小值的给负号, 下面 n - m 的前一半大的值的为正号.
那么现在为限制只进行交换k次呢?有什么影响
上面的一种无穷的交换次数的那个可以知道, 我们主要就是要交换这么一种情况,
上面的前一半大值中的a1 , 与下面的前一半大值中的b1刚好相对, 按照最大的情况需要 给a1 , b1都赋正号,但是这时候,b1 > a1了,使得b1为正号,a1为负号,
所以我们找到一个这样的 a2, a2为后一半的小值, b2也为后一半的小值, a2与b2相对的a2 ,让a2与a1交换,使得a1 变为正,a2变为负,b1为正,b2为负.从而达到最大.
也就是我们赋值正负号后,得到的结果是这样的
两个正号的在一组了, 两个负号的在一组了, 于是需要进行交换,才能满足最大情况出现.
于是重点来了!
一次正负交换能够增加多大的值?
首先 x1 ,x2为前半段正号值, x3,x4为后半段负号值
则,转换后的新值为 x1 + x2 - x3 - x4.
①: x1 大于 x2 , x3 大于 x4
原值为: x1 - x2 + x3 - x4
新值 - 原值:2 * x2 - 2 * x3
②:x1 < x2 , x3 > x4
原值: x2 - x1 + x3 - x4
新值 - 原值 : 2 * x1 - 2 * x3
③:x1 < x2 , x3 < x4
原值: x2 -x1 + x4 - x3
新值 - 原值: 2 * x1 - 2 * x4
④:x1 > x2 ,x3 < x4
原值: x1 - x2 + x4 - x3
新值 - 原值: 2 * x2 - 2 * x4
从中可以看出什么呢?
原来每一次交换后,新值相对于原值都会增加: 2 * min(x1,x2) - 2 * max(x3 , x4)
而最优的这些情况,也就说明这些情况是最大max(2 * min(x1,x2) - 2 * max(x3 , x4)), 以及次大max, 次次大..... , 因为我们每次考虑的都是最优的情况, 所以实际上每次加入进来的都是最优的情况,也就是x1,x2为大值, x3,x4值为小值. 虽然存在x1,x2一大一小,x3,x4全小....,但每次都加入最优情况(正正 负负进行交换),所以这个不会出现而被加入而影响结果.
所以我们只要将前k个加进去即可.
那么问题来了? 如果没有k个怎么办.
那么就不在加了即可.
此题中的恰好k个实际上与至多k个是等价的.
因为在小于k个已经到达了最大情况,则 剩下的就不断的正的和正的交换,或者负的和负的交换即可. 值不会发生改变.
至于负的情况,就更不需要考虑进行了.
每次都是最优的,得到的最后结果自然就是最优了.
同时还有一个地方要注意:
那就是n == 2的时候, 如果k是偶数,则必定为不换,如果k为奇数的话,则必定代表着互换了a[1],a[2].
为什么要注意这个呢?
因为 n == 2的时候,我们之前分析的恰好k个 与 至多k个在这个情况下就不是等价的了.
因为当我们通过小于 k 次到达了最大值后, 后面可以进行多次为0的情况凑到恰好为k次.
而当n == 2 则无法进行其他为0的情况, 也就是凑不成k次.
思路2:
我们通过区间的方式,会发现两个区间有交集的话,每次交换获利都是增加,并且增加固定值2 * (min(a[j],b[j]) - max(b[i],a[i]) ).
而如果两个区间有交集,只有两种情况。
1.获利为0
2.获利减少。
所有当有交集的互换完了,还没到达k次,那么就用获利为0的方式凑到k次即可。
理解方式2:更容易理解的方式,使用区间的方式进行理解:
当给出的{ai,bi} 与 {aj , bj}之间无交集的时候,你会发现, 将ai,aj进行交换,都会更优,因为原长度为两段黑色总和, 新长度为两段蓝色总和. 而蓝色总和要长于黑色总和
而这时你需要注意到一点,就是蓝色部分总和 比 黑色部分多加的部分就是2 * (min(a[j],b[j]) - max(b[i],a[i]) ). 所以在原基础上,加上一个 2 * (min() - max())即可.
有交集的具体情况:
①长度不变, ②长度减少 ③长度不变 ④长度减少
⑤长度不变 ⑥长度减少 ⑦长度减少 ⑧长度不变
所以有交集的话,最好情况是无损失,最坏情况则是有损失, 所以我们需要选择的便是不断重复无损失的情况,直到凑到k次.
代码实现
# include <iostream>
# include <algorithm>
using namespace std;
const int N = 5e5 + 10;
int a[N];
int b[N];
int maxv[N],minv[N];
int n,k;
bool cmp(int a , int b)
{
return a > b;
}
int main()
{
scanf("%d %d",&n,&k);
for(int i = 1 ; i <= n ; i++)
{
scanf("%d",&a[i]);
}
for(int i = 1 ; i <= n ; i++)
{
scanf("%d",&b[i]);
maxv[i] = max(a[i],b[i]);
minv[i] = min(a[i],b[i]);
}
long long ans = 0;
if(n == 2)
{
if(k % 2 == 1) // k为奇数,则必互换了
{
swap(a[1],a[2]);
}
ans =ans + abs(a[1] - b[1]) + abs(a[2] - b[2]);
}
else
{
for(int i = 1 ; i <= n ; i++)
{
ans += abs(a[i] - b[i]);
}
sort(minv + 1 , minv + 1 + n , cmp);
sort(maxv + 1 , maxv + n + 1);
for(int i = 1 ; i <= n && i <= k; i++)
{
if(minv[i] > maxv[i])
{
ans += 2 * (minv[i] - maxv[i]);
}
else
{
break;
}
}
}
printf("%lld\n",ans);
return 0;
}