贪心 + 数论 + 求abs时的技巧:Game of Swapping Numbers

题目链接: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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值