题面
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); } /* */
每两个比较完后,就更新。
其实时间就费在每一次都要将序列重新排序,每一次都是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,只是理解了思想,具体还要看实现(记得填坑)。