zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    HTML 转 PDF 之 wkhtmltopdf 工具精讲
    oracle学习之数据库数据保存成文件
    字体单位大小对照换算表(字号、磅、英寸、像素)
    mui 注意事项
    hbuilder header消失
    C# salt+hash 加密
    判断二个文件是否相同
    html转pdf
    Oracle中Clob类型处理解析:ORA-01461:仅可以插入LONG列的LONG值赋值
    【Django】Django 直接执行原始SQL 如何防止SQL注入 ?
  • 原文地址:https://www.cnblogs.com/Qing-LKY/p/CF1467C-solution.html
Copyright © 2011-2022 走看看