• 2019暑假集训DAY1(problem3.play)(单调栈思想)


    题面

    play

    题目大意

    这个位面存在编号为1~2N的2N个斗士,他们正为争夺斗士大餐展开R轮PVP,每个斗士i都有一个固有APM ai,和一个初始斗士大餐储量 bi。每轮开始前以及最后一轮结束之后,
    2N个斗士会重新按照各自斗士大餐的储量进行排序(斗士大餐储量相同时编号小的靠前),每轮中,第1名和第2名PVP,第3名和第4名PVP,……第2k-1名和第2k名PVP,第2N-1名和第2N名PVP。
    而每场一对一的PVP都非常无聊,总是两个斗士中APM高的获胜,另一方失败;或者APM相同的两方取得平手。每轮赛后获胜方获得2份斗士大餐,平均双方均获得1份斗士大餐。
    求输出最后排名从小到大的各斗士编号。

    输入格式

    第一行两个整数N、R;
    第二行2N个整数,bi;
    第三行2N个整数, ai;

    输出格式

    2N个整数,R轮比赛后排名从小到大的各斗士编号;

    样例输入

    10 10
    0 10 49 24 7 1 64 8 52 81 4 9 40 17 52 17 40 0 97 77
    0 1 0 1 1 1 0 2 1 0 0 2 1 1 2 0 1 1 1 0

    样例输出

    19 10 20 7 15 9 13 17 3 4 14 12 8 2 16 5 6 18 11 1

    数据范围

    10%的数据:N≤10,R≤10,ai≤1e8,bi≤1e8
    30%的数据:N≤1e2,R≤60,ai≤1e8,bi≤1e8
    70%的数据:N≤1e4,R≤60,ai≤1e8,bi≤1e8
    100%的数据:N≤1e5,R≤60,ai≤1e8,bi≤1e8


    一开始看到这道题,确实是没有什么思路的(可能是上午的课真的还听的蛮模糊的),就只打了个暴力单纯模拟,数据很水,没想到还能得70分

    考试时的代码

    #include<bits/stdc++.h>
    #define N 200003
    using namespace std;
    int read()
    {
        int f=1,x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    struct dou{
        int a,b,ord;
    }w[N];
    bool cmp(const dou &x,const dou &y)
    {
        if(x.b==y.b)return x.ord<y.ord;
        return x.b>y.b;
    }
    int main()
    {
        freopen("play.in","r",stdin);
        freopen("play.out","w",stdout);
        int n=read(),r=read();
        n=n*2;
        for(int i=1;i<=n;++i)
        {
            w[i].b=read();w[i].ord=i;
        }
        for(int i=1;i<=n;++i)
          w[i].a=read();
        while(r--)
        {
            sort(w+1,w+1+n,cmp);
            for(int i=1;i<=n;i+=2)
            {
                if(w[i].a>w[i+1].a)w[i].b+=2;
                else if(w[i].a<w[i+1].a)w[i+1].b+=2;
                else w[i].b++,w[i+1].b++;
            }
        }
        sort(w+1,w+1+n,cmp);
        for(int i=1;i<=n;++i)
          printf("%d ",w[i].ord);
    } 
    /*
    
    */
    70分

    每两个比较完后,就更新。

    其实时间就费在每一次都要将序列重新排序,每一次都是O(nlogn)的,于是轻轻松松炸掉。


    那么正解的思路是什么呢?

    再仔细分析题目,会发现每一次更新后每一个数可分为这样三种情况:

    1.值+2

    2.值不变

    3.值加一

    因为每一次操作前都是排好序的,(以第一种为例)那么在同一种情况中,前面值+2的肯定比后面值也要+2的大,毕竟原来排序就在前面,都加了2后,在前面的还是在前面。

    那么就发现,对于每一种情况都是满足单调性的。

    于是我们可以开三个数组A,B,C,分别存这三种情况,再以归并排序的思想去合并,就可以在差不多O(2*n)(其实就可以看作是O(n))的时间里完成一个在这一次操作后从大到小的序列,而不是用O(nlogn)的时间来快排。R也不大,于是这可以A掉这道题了。

    代码很简短,也很好理解。

    #include<bits/stdc++.h>
    #define N 200003
    using namespace std;
    int read()
    {
        int f=1,x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    struct dou{
        int a,b,ord;
    }w[N],A[N],B[N],C[N];
    bool cmp(const dou &x,const dou &y)
    {
        if(x.b==y.b)return x.ord<y.ord;
        return x.b>y.b;
    }
    int cnta=0,cntb=0,cntc=0;
    int main()
    {
        freopen("play.in","r",stdin);
        freopen("play.out","w",stdout);
        int n=read(),r=read();
        n=n*2;
        for(int i=1;i<=n;++i)
        {
            w[i].b=read();w[i].ord=i;
        }
        for(int i=1;i<=n;++i)
          w[i].a=read();
        sort(w+1,w+1+n,cmp);
        while(r--)
        {
            cnta=0;cntb=0;cntc=0;//记得清零,每一次都以相同的步骤来更新序列保持b从大到小 
            for(int i=1;i<=n;i+=2)//分别存入三个数组中,保持单调性 
            {
                if(w[i].a>w[i+1].a)
                {
                    w[i].b+=2;
                    A[++cnta]=w[i];
                    B[++cntb]=w[i+1];
                }
                else if(w[i].a<w[i+1].a)
                {
                    w[i+1].b+=2;
                    A[++cnta]=w[i+1];
                    B[++cntb]=w[i];
                }
                else 
                {
                    w[i].b++;w[i+1].b++;
                    C[++cntc]=w[i];
                    C[++cntc]=w[i+1];
                }
            }
            int s1=1,s2=1,sum=1;
        //    printf("!!%d %d %d
    ",cnta,cntb,cntc);
            while(s1<=cnta&&s2<=cntb)//归并排序的思想 
            {
                if(A[s1].b>B[s2].b||(A[s1].b==B[s2].b&&A[s1].ord<B[s2].ord)) w[sum++]=A[s1++];
                else if(A[s1].b<B[s2].b||(A[s1].b==B[s2].b&&A[s1].ord>B[s2].ord)) w[sum++]=B[s2++];
            }
            while(s1<=cnta) w[sum++]=A[s1++];
            while(s2<=cntb) w[sum++]=B[s2++];
            for(int i=1;i<=cnta+cntb;++i) A[i]=w[i];//因为有三个数组,所以这是必要的。先把AB合并后,再以相同的思想合并C,保证正确性。 
            s1=1;s2=1;sum=1;//或者说,直接用三个指针来比较?但代码没有这样容易实现呢(因为还有一个b相同比较ord的条件)(反正这样写时间复杂度也是保证了的) 
            while(s1<=cnta+cntb&&s2<=cntc)
            {
                if(A[s1].b>C[s2].b||(A[s1].b==C[s2].b&&A[s1].ord<C[s2].ord)) w[sum++]=A[s1++];
                else  w[sum++]=C[s2++];
            }
            while(s1<=cnta+cntb)w[sum++]=A[s1++];
            while(s2<=cntc)w[sum++]=C[s2++];
        }
        for(int i=1;i<=n;++i)
          printf("%d ",w[i].ord);
    } 
    /*
    
    */
    单调性

    好吧,其实这道题和今天上午讲的NOIP 2016 D2T2 蚯蚓 有相同的思想。

    在不考虑q的时候,其实就可以把每一次切断后的较长蚯蚓放入一个单调的数组A,较短蚯蚓放入一个单调数组B,同样的,这也是满足单调性的(因为q是一定的)。

    然后每一次要切蚯蚓的时候,就是取出A,B,还有原序列(还没被I切过的那些蚯蚓)的“队首”(这是必要的,因为会存在原序列的“队首”小于A或B的“队首”,前面切过的某条实在是太长了)。

    那q怎么办?(借鉴洛谷题解)

    其实最麻烦的就是每一其他蚯蚓的长度要增加q,直接暴力的话就很费时间。

    我们可以省去每一秒增加每只蚯蚓的长度这个操作,转换成在查询砍那只蚯蚓时,把增加的长度算到蚯蚓的总长度上。

    emmm,只是理解了思想,具体还要看实现(记得填坑)。

  • 相关阅读:
    iOS 多态的简单思想
    WPF SDK研究 之 AppModel
    SQL实现统计字符串出现的频次
    Python学习心得(一) 列表List
    GreenPlum之数组合并取交集及行变列、列变行函数
    SQL Server日常总结生僻小技巧 10个可能有八个平时都没用到过
    GreenPlum之按月份或季度实现行转列
    GreenPlum之生成月份字典表
    postgresql数据库中判断是否是数字和日期时间格式函数
    maven构建报错org.apache.maven.lifecycle.LifecycleExecutionException
  • 原文地址:https://www.cnblogs.com/yyys-/p/11160681.html
Copyright © 2020-2023  润新知