zoukankan      html  css  js  c++  java
  • CF521D Shop 贪心

    题意:

    (n)个数,有(m)个操作,形如:
    1,将(x_i)​改成(val_i)
    2,将(x_i)加上(val_i)
    3,将(x_i)​乘上(val_i)
    其中第(i)个操作的编号为(i). 现在你可以从中选择最多(k)个操作(不能重复选),并按一定顺序执行,使得(prod_{i = 1}^{n}x_i)最大。
    第一行输出选择的操作个数,第二行按执行顺序输出方案。

    题解:

    如果只有3号操作,那么因为最后的答案是乘起来,而不是加起来,因此每个乘,不管对哪个数进行操作,对ans的贡献都是相同的。所以我们可以直接按操作的值排序,贪心的取值即可。
    但现在有3种操作,我们可以考虑将其转换为3号操作。
    首先我们有个比较显而易见的结论:对于已经选定的(k)个操作,我们一定是先赋值,再加,再乘,即按123的操作依次进行。
    我们先考虑是否可以将加法变为乘法。
    基于贪心的思想,对于同一个数,我们选择加法操作,肯定是先选加得多的,因此我们将加法从高到底排序,依次操作。
    那么每个加法的使用前提都是前一个加法已经被使用过了。
    因此对于每个加法,它的使用前提都是固定的,即对于每个加法操作,都是把某个值为(x)的数变为一个值为(y)的数,其中(x)是这个数经过它前面的所有加法后变成的数,也就是一段前缀和。
    因为每个加法使用的条件都是固定的,因此我们可以把任意一个加法看成一个(val)(frac{x}{y})的乘法,于是我们就可以按照上面的方法进行贪心了。

    那么如何保证这样一定合法呢?
    可以知道,这样合法,当且仅当我们取的每段加法都是一段前缀,可以证明,排在后面的加法,转化为乘法后肯定也没有之前优,因此如果我们取了一个加法,那么这个加法之前的加法肯定也都取过了。
    一个简单的证明:
    (a > b > c > d),试证明前面的乘法肯定要比后面的大,即证:

    [frac{c}{a + b} > frac{d}{a + b + c} ]

    [c(a + b + c) > d (a + b) ]

    显然成立。

    又因为乘法是没有先后顺序的,因此不管我们取的加法是不是断断续续取的,反正我们可以把它调整到正确的顺序。

    那么现在来考虑操作1.
    首先对于任意一个数而言,最多只会进行一次操作一(因为进行多次就相当于之前的操作都无效了)。
    那么我们先去掉较劣的操作1,对于每个数都只保留最大的那个(如果有的话)
    可以观察到,初步筛选后,操作1实际上就相当于把原数加上一个值变为了(val_i),因此我们可以以这个增量为新的(val_i),把这个操作转换为加法,然后当做一个普通的加法放到操作2的数组当中去sort。

    这样看上去打破了我们之前按123依次进行的约定?
    其实并没有,考虑2种情况。
    1,我们在取值时没有取到这个赋值操作。那么显然不会产生影响
    2,我们在取值时取到了这个赋值操作,那么因为乘法可以打乱顺序,因此我们依然可以看做是在最开始进行了这个赋值操作,那么这个操作确实可以起到加上一个值的效果,并不会违背我们之前推出的性质。

    简单来说,做这道题的时候注意一下几点:
    1,将3个操作都转换为乘法
    2,运用贪心的原则
    3,记住乘法是可以随意调整顺序的。
    4,因为最后求的是乘积,所以每个乘法对答案的贡献都是相同的。

    其中第3点非常重要,这题所有操作的正确性(包括贪心和转换部分)都是基于这个性质的!

    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define AC 101000 
    #define LL long long
    
    int n, m, k, tot1, tot2, tot3, cnt;
    LL s[AC];
    
    struct node{
        int opt, id, x;LL v, w;//w为分母
    }m1[AC], m2[AC], m3[AC], ans[AC];//不需要知道具体值……所以减1再比较也不影响结果,知道哪些是我要的操作就好了
    
    inline bool cmp1(node a, node b){return (a.x != b.x) ? (a.x < b.x) : (a.v < b.v);}//给操作1去重
    inline bool cmp2(node a, node b){return (a.x != b.x) ? (a.x < b.x) : (a.v > b.v);}//上面去重所以从小到大,这里贪心所以从大到小
    inline bool cmp3(node a, node b){return a.opt < b.opt;}//先1再2再3,且取走的加法是一段前缀才能保证结果正确性
    inline bool cmp4(node a, node b){return a.v * b.w > a.w * b.v;}//分数比较
    
    inline int read()
    {
        int x = 0;char c = getchar();
        while(c > '9' || c < '0') c = getchar();
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x;
    }
    
    void pre()
    {
        n = read(), m = read(), k = read();
        for(R i = 1; i <= n; i ++) s[i] = read();
        for(R i = 1; i <= m; i ++)
        {
            int a = read(), b = read(), c = read();
            if(a == 1 && c > s[b]) m1[++ tot1] = (node){1, i, b, c, 1};//如果改了反而不优就算了
            else if(a == 2) m2[++ tot2] = (node){2, i, b, c, 1};
            else if(a == 3) m3[++ tot3] = (node){3, i, b, c, 1};//默认分母为1
        }
    }
    
    void build()
    {
        sort(m1 + 1, m1 + tot1 + 1, cmp1);	
        int tmp = 0;
        for(R i = 1; i <= tot1; i ++)
            if(m1[i].x != m1[i + 1].x) m1[++ tmp] = m1[i];//对于同一个数的赋值操作只保留最大的,否则影响贪心正确性
        tot1 = tmp;
        for(R i = 1; i <= tot1; i ++) 
            m1[i].v = m1[i].v - s[m1[i].x], m2[++ tot2] = m1[i];//把赋值变成加法后加入m2中
        sort(m2 + 1, m2 + tot2 + 1, cmp2);//因为要每个数单独看,所以还是要先按数排序,再按大小排序
        LL sum = 0;
        for(R i = 1; i <= tot2; i ++) 
        {
            if(m2[i].x != m2[i - 1].x) sum = s[m2[i].x];//对于新的一段就重置一下
            m2[i].w = sum, sum += m2[i].v;//v不用修改,因为统一-1了,所以分子要-sum
        }	
        for(R i = 1; i <= tot3; i ++) m3[i].v --;//统一-1不会影响比较结果
        for(R i = 1; i <= tot2; i ++) m3[++ tot3] = m2[i];
    }
    
    void work()
    {
        sort(m3 + 1, m3 + tot3 + 1, cmp4);
        int tmp = min(tot3, k);
        for(R i = 1; i <= tmp; i ++) ans[++ cnt] = m3[i];	
        printf("%d
    ", cnt);
        sort(ans + 1, ans + cnt + 1, cmp3);
        for(R i = 1; i <= cnt; i ++) printf("%d ", ans[i].id);
        printf("
    ");
    }
    
    int main()
    {
        //freopen("in.in", "r", stdin);
        pre();
        build();
        work();
    //	fclose(stdin);
        return 0;
    }
    
  • 相关阅读:
    归并排序(Merge Sort)
    AtCoder AGC035D Add and Remove (状压DP)
    AtCoder AGC034D Manhattan Max Matching (费用流)
    AtCoder AGC033F Adding Edges (图论)
    AtCoder AGC031F Walk on Graph (图论、数论)
    AtCoder AGC031E Snuke the Phantom Thief (费用流)
    AtCoder AGC029F Construction of a Tree (二分图匹配)
    AtCoder AGC029E Wandering TKHS
    AtCoder AGC039F Min Product Sum (容斥原理、组合计数、DP)
    AtCoder AGC035E Develop (DP、图论、计数)
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10269471.html
Copyright © 2011-2022 走看看