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

Posted yyys-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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\n",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,只是理解了思想,具体还要看实现(记得填坑)。

技术图片

以上是关于2019暑假集训DAY1(problem3.play)(单调栈思想)的主要内容,如果未能解决你的问题,请参考以下文章

暑假集训Day1 B(拓展欧拉定理)

暑假集训Day1 A(gcd)

2021年SWPUACM暑假集训day1二分算法

暑假清北学堂集训笔记

「总结」2019暑假集训

2019暑假集训 8/16