【CF1467C】Three Bags 题解

题目链接

题意简介

有三个集合,里面分别有 n1,n2,n3 个数。
在每次操作中,你可以选择其中的两个不同的集合(下记为 A 和 B),在 A 中取一个数 a,B 中取一个数 b,然后将 a 减去 b,并在 B 集合中删去这个数。
(原文 remove b from the second bag and replace a with a−b in the first bag)
现在你需要通过若干次这样的操作,使得:有且只有一个集合有数字,且这个集合中的数字有且只有一个
(原文 You have to perform these operations in such a way that you have exactly one number remaining in exactly one of the bags (the other two bags being empty))
现在的问题是,你通过操作,最终留下来的数字最大可以是多少?

思路分析

首先不难注意到,一个数从一个集合挪到另一个集合,它的贡献要乘以 -1。换句话说,如果我们希望某个数从集合 A 移动到集合 B 后贡献为正,那么我们需要至少经过一次中间集合 C

我们希望答案最大,那么自然是希望尽可能大/多的数的贡献为正。

接下来观察并手推一下题目中的两个样例,我们不难发现,各个数对答案的贡献是这样的(可能有不同的方案,但求出的最优解是一样的):
样例 1:A {-1 -2} B {+6 +3 +4 +5} C {+5}
样例 2:A {+7 +5 +4} B {-2 +9} C {+7 -1}
观察到,两个样例在解出最优解的情况下,贡献为负的值总为两个

那么,是否只要在三个集合中挑出两个最小值,使它们的贡献为负就可以了呢?

答案是否定的。让我们仔细考虑一下,为什么贡献为负的值总可以是两个。

不难发现,在任何情况下,我们总能找到下面这种情形:

在图中,最终留下来的数是 y,且除了分属于两个不同集合的 x 和 z 以外,所有数的贡献都是正的(挪一次变为负 借由中介 x 和 y 再挪一次变为正)。

可以看出,由于中转媒介必须出现在除了最终答案所在集合(此处为 B)及其自身外的另一个集合里。例如想要把 A 的数字贡献转正,需要在 C 中有一个数来中转,把 C 的数字贡献转正,则需要 A 中有一个数字来中转。中转的数字由于只移动了一次,所以贡献为负。

这就解释了为什么负贡献的值至少需要两个。(此处先不考虑某个集合中只有一个数的情形)

P.S. 你可能会说那如果我不要马上挪到 B 集合,而是在 A 和 C 之间多挪几次呢。那样的操作是没有意义的,只会使贡献为负的数字增加。

不难由此得出第一种一种可能达到最优解的方案:
挑两个集合,使这两个集合中的最小值的贡献为负

但是我们发现,这种情形显然不适用于样例 1,因为样例 1 的两个贡献为负的数字出现在了同一个集合中。

不妨考虑一下,要怎样做,才可以使某两个集合中所有数的贡献都为正。

于是,有了下图的情形。牺牲某个一个集合,使其它集合的贡献为正

原因很简单。如果你希望 A 集合中也有正贡献的话,那你一定会使 B 和 C 中的某个数的贡献转为负,这还不如第一种方案。

这依然是一道考虑清楚了就非常简单的题目。具体细节见代码。

代码库

#include <cstdio>
#include <algorithm>
using namespace std;
// 记得开 long long
typedef long long ll;
const int N=3e5+5;
ll n1,n2,n3,A[N],B[N],C[N],sum[3],res,minn[3];
int main(){
    scanf("%lld%lld%lld",&n1,&n2,&n3);
    minn[0]=minn[1]=minn[2]=1e18;
    for(int i=1;i<=n1;i++) scanf("%lld",A+i),sum[0]+=A[i],minn[0]=min(minn[0],A[i]);
    for(int i=1;i<=n2;i++) scanf("%lld",B+i),sum[1]+=B[i],minn[1]=min(minn[1],B[i]);
    for(int i=1;i<=n3;i++) scanf("%lld",C+i),sum[2]+=C[i],minn[2]=min(minn[2],C[i]);
    // tmp 是三个最小之和 tt 是所有数之和
    ll tmp=minn[0]+minn[1]+minn[2],tt=sum[0]+sum[1]+sum[2];
    // 集合 i 贡献转负 别忘了*2
    for(int i=0;i<3;i++) res=max(res,tt-sum[i]*2); 
    // 除了集合 i 以外的两个集合的最小值贡献转负
    for(int i=0;i<3;i++) res=max(res,tt-(tmp-minn[i])*2); 
    printf("%lld",res);
    return 0;
}
原文地址:https://www.cnblogs.com/Qing-LKY/p/CF1467C-solution.html