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;
    }
    
  • 相关阅读:
    接口interface
    枚举类型
    编写Hello World ts程序
    TypeScript基本类型
    初始TypeScript
    session和cookie自动登录机制
    奇辉机车车号自动识别系统介绍
    AForge.NET 设置摄像头分辨率
    工作感概—活到老xio到老
    Scala学习二十二——定界延续
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10269471.html
Copyright © 2011-2022 走看看