[cf1491I]Ruler Of The Zoo

为了统一描述,下面给出题意——

有$n$只动物,编号为$i$的动物有属性$a_{i,j}$($0\le i<n,0\le j\le 2$)

初始$n$只动物从左到右编号依次为$0,1,...,n-1$,重复以下过程:

(初始$j=0$,假设最左边的两只动物编号依次为$x$和$y$)

1.比较$a_{x,j}$和$a_{y,0}$,将其中较小的那只动物移动到最右边

2.若比较中$a_{x,j}>a_{y,0}$,则令$j$增加1且若$j\ge 3$则游戏结束,否则令$j=1$

输出游戏结束时的$x$以及过程执行的次数,或游戏永远无法结束

$4\le n\le 6\times 10^{3}$,$0\le a_{i,j}\le 10^{9}$,$a_{i,1}<\min(a_{i,0},a_{i,2})$且保证$a_{i,j}$各不相同

 

以下,我们将题解分为三部分,可能与原题解略有不同

第一部分:颜色的定义即变化

为了方便,我们先手动模拟第一次(即比较$a_{0,0}$和$a_{1,0}$)

对于某一个时刻,我们假设所有动物从左到右编号依次为$id_{0},id_{1},...,id_{n-1}$,给每一只动物一个颜色(红色或非红色),其中$id_{i}$为红色当且仅当$a_{id_{i},0}<a_{id_{i-1},1}$

(特别的,$id_{0}$为“红色”当且仅当$a_{id_{0},0}<a_{id_{n-1},1}$)

首先,考虑$id_{0}$一定非红(手动模拟一次后),证明如下:

考虑$id_{0}$和$d_{n-1}$也就是上一次比较的两只动物且$id_{0}$较大,而若其为红色即$a_{id_{0},0}<a_{id_{n-1},1}$,那么唯一有可能比$id_{n-1}$大的仅有$a_{id_{0},2}$,即比较时$j=2$且胜利后变为$j=3$,游戏已经结束

接下来需要分析每一次颜色的变化,显然将$x$移动到最右边是不会导致动物颜色变化的,只有当$a_{x,j}>a_{y,0}$,也就是$y$移动到最右边时会变化,此时对$j$分类讨论:

1.当$j=1$时,由于$a_{x,1}>a_{y,0}$,根据定义也就是$y$为红色,再令$y'$为$y$下一个位置(即$id_{2}$),颜色会发生变化的也就是$x$、$y$和$y'$这三个位置

对于$x$,其本来是非红色,且$y$为红色可得$a_{y,0}<a_{x,1}$,再根据$a_{i,1}<a_{i,0}$即可得$a_{y,1}<a_{x,0}$,也就是说$x$仍然是非红色

对于$y$,其本来是红色,但根据颜色的信息无法确定其最终的颜色

对于$y'$,对其初始颜色分类讨论:

(1)若其为红色,即$a_{y',0}<a_{y,1}$,不难得到$a_{y',0}<a_{x,1}$,即仍然是红色

(2)若其为非红色,即$a_{y',0}>a_{y,1}$,同样根据颜色的信息无法确定其最终的颜色

2.当$j=2$时,此时若$a_{x,j}>a_{y,0}$即$x$获得胜利,也就没有颜色变化了

综上分析,颜色变化仅在$j=1$且$a_{x,j}>a_{y,0}$($y$移动到最右边)时红色的$y$变为非红色,或非红色的$y'$变为红色(这些变化有可能不发生,但发生的一定是这些变化)

但对于第2种$y'$变为红色,对于下一次,其是红色即$a_{y',0}<a_{x,1}$,同时$a_{x,1}<a_{x,2}$,那么$x$就胜利了

因此,至多只有一次非红色变为红色,根据红色的数量总在$[0,n]$之间,红色变为非红色的次数也至多只有$n+1$次,另外还有至多1次结束操作

对于使得颜色变化或结束的操作,称作特殊操作,数量为$o(n)$

第二部分:操作分组

下面考虑将$n-1$次操作合并为一组操作(手动模拟的第一次操作不计入其中,即$1+(n-1)+(n-1)+...$的形式),显然包含特殊操作的组数也是$o(n)$的

注意到一组操作中,如果初始状态是$id_{i}$,那么第$i$次操作的$y$即为$id_{i}$,且最终放到最后的也就作为下一次的$id\ '_{i}$,最后一轮中未放到最后的是$id\ '_{0}$

如果已经确定某一组内不包含特殊操作,考虑这一组操作的效果——

首先有以下性质:在这一组中的操作,$a_{x,j}>a_{y,0}$当且仅当$y$为红色

$j=1$根据定义是显然的;$j=2$则由于没有结束操作,比较结果必然是$a_{x,2}<a_{y,0}$,也即可推出$a_{x,1}<a_{y,0}$,那么$y$即为非红色,符合性质

且由于没有发生使颜色变化操作,这个$y$的颜色即初始状态中的颜色

由此归纳可得:第$i$次操作时$x$为$[0,i)$之间最后一个非红色的$id_{j}$($id_{0}$为非红色总是存在)且$y=id_{i}$

下面来考虑新的编号序列$id\ '_{i}$,对于$i\ge 1$的$id\ '_{i}$,对第$i$次的$y=id_{i}$分类讨论来确定其值:

1.若$y$为红色,也就是$id\ '_{i}=id_{i}$

2.若$id_{i}$为非红色,那么$id\ '_{i}=x$,也就是其之前第一个非红色的位置

(特别的,$id\ '_{0}$是最后一次操作中未放到最后的,类似归纳过程可得即最后一个非红色的$id_{j}$)

总得来说,最后的$id\ '_{i}$就是在$id_{i}$的基础上,红色位置的不变,非红色的位置向右旋转一圈,之后$j$的值是取决于最后一个位置,即若是红色$j=2$,否则$j=1$

第三部分:快速找到下一个包含特殊操作的组

下面,如果能对于一个$id_{i}$,我们需要知道执行多少组操作(旋转多少次)后可以使得下一组操作中包含特殊操作,再暴力执行这些操作(旋转和下一组)即可

旋转和暴力执行一组操作都是可以做到$o(n)$的,那么关键就是如何$o(n)$找到这个次数

事实上,去除一些细节问题后,比较复杂的主要有两点:

1.对于每一个红色的位置$id_{i}$,找到旋转最少的次数使得其上两个的红色位置$id_{j}$满足$a_{id_{j},1}<a_{id_{i},0}$

2.对于所有相邻的非红色的位置$id_{i}$和$id_{j}$(并不一定有$|i-j|=1$,允许中间有红色),满足$a_{id_{i},2}>a_{id_{j},0}$找到其旋转的最少次数使得其恰好夹着一个红色位置

对于第一点,可以维护一个单调栈,将非红色的位置$a_{id_{j},1}$的后缀最小值找出(很明显既靠后又小的一定优),之后当有红色位置,将其从左边不断弹出比当前位置小的即可

之后由于旋转是环,将序列重复两次即可,另外旋转是非红色的位置,需要维护非红色位置的前缀和

对于第二点,将所有非红色位置提出后,从前往后记录最后一个满足此性质的,当遍历到红色(两个非红色之间有间隙),计算最后一个移动到这个红色的距离即可

略微补充一下细节问题:

1.如果出现相邻的红色,可以直接判定这一轮一定结束;

2.需要先执行若干次,来保证$j$与最后一个位置颜色相同(只执行一次并不一定足够,因为可能有连续两次都包含特殊操作)

总复杂度即$o(n^{2})$,可以通过

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define N 6005
  4 vector<int>v;
  5 deque<int>q;
  6 int n,id[N],idd[N],tot[N<<1],a[N][3];
  7 long long sum;
  8 int red(int k){
  9     if (!k)return 0;
 10     return a[id[k]][0]<a[id[k-1]][1];
 11 }
 12 int find(){
 13     int j=red(n-1)+1,ans=0x3f3f3f3f;
 14     if ((j==2)&&(red(1)))return 0;
 15     for(int i=1;i<n;i++)
 16         if ((red(i-1))&&(red(i)))return 0;
 17     tot[0]=1;
 18     for(int i=1;i<2*n;i++)tot[i]=tot[i-1]+(!red(i%n));
 19     q.clear();
 20     for(int i=0;i<2*n;i++)
 21         if (red(i%n)){
 22             while ((!q.empty())&&(a[id[q.front()%n]][1]<a[id[i%n]][0])){
 23                 ans=min(ans,tot[i]-tot[q.front()]-1);
 24                 q.pop_front();
 25             }
 26         }
 27         else{
 28             while ((!q.empty())&&(a[id[q.back()%n]][1]>a[id[i%n]][1]))q.pop_back();
 29             q.push_back(i);
 30         }
 31     v.clear();
 32     for(int i=0;i<n;i++)
 33         if (!red(i))v.push_back(i);
 34     if ((j==2)&&(a[id[v[0]]][2]>a[id[v[1]]][0]))return 0;
 35     int lst=-1;
 36     if (a[id[v.back()]][2]>a[id[v[0]]][0]){
 37         lst=0;
 38         if (j==2)ans=min(ans,1);
 39     }
 40     for(int i=1;i<v.size();i++){
 41         if (a[id[v[i-1]]][2]>a[id[v[i]]][0]){
 42             if (v[i-1]+1<v[i])return 0;
 43             lst=v[i];
 44         }
 45         if ((lst>=0)&&(v[i-1]+1<v[i]))ans=min(ans,v[i]-lst-1);
 46     }
 47     if (lst>=0){
 48         if (j==2)ans=min(ans,n-lst);
 49         for(int i=1;i<v.size();i++)
 50             if (v[i-1]+1<v[i])ans=min(ans,v[i]+(n-lst)-1);
 51     }
 52     if (j==2){
 53         if (a[id[v[0]]][0]<a[id[v.back()]][1])return 0;
 54         for(int i=1;i<v.size();i++)
 55             if (a[id[v[i]]][0]<a[id[v[i-1]]][1])ans=min(ans,n-v[i]);
 56     }
 57     if (ans>n)return -1;
 58     return ans;
 59 }
 60 void turn(int k){
 61     sum+=k*(n-1);
 62     v.clear();
 63     for(int i=0;i<n;i++){
 64         if (red(i))idd[i]=id[i];
 65         else v.push_back(i);
 66     }
 67     for(int i=0;i<v.size();i++)idd[v[i]]=id[v[(i+v.size()-k)%v.size()]];
 68     memcpy(id,idd,sizeof(id));
 69 }
 70 int calc(int j){
 71     int lst=id[0];
 72     for(int i=1;i<n;i++){
 73         sum++;
 74         if (a[lst][j]>a[id[i]][0]){
 75             idd[i]=id[i];
 76             if (++j>=3){
 77                 printf("%d %lld",lst,sum);
 78                 exit(0);
 79             }
 80         }
 81         else{
 82             idd[i]=lst;
 83             lst=id[i];
 84             j=1;
 85         }
 86     }
 87     idd[0]=lst;
 88     memcpy(id,idd,sizeof(id));
 89     return j;
 90 }
 91 int main(){
 92     scanf("%d",&n);
 93     for(int i=0;i<n;i++)
 94         for(int j=0;j<3;j++)scanf("%d",&a[i][j]);
 95     for(int i=1;i<n;i++)id[i]=i+1;
 96     sum=1;
 97     if (a[0][0]<a[1][0]){
 98         id[0]=1;
 99         id[n-1]=0;
100     }
101     else{
102         id[n-1]=1;
103         id[0]=0;
104     }
105     int j=1;
106     while (1){
107         while (j!=red(n-1)+1)j=calc(j);
108         int i=find();
109         if (i<0){
110             printf("-1 -1");
111             return 0;
112         }
113         turn(i);
114         j=calc(j);
115     }
116 }
View Code

 

posted @ 2021-03-28 14:21  PYWBKTDA  阅读(131)  评论(0编辑  收藏  举报