[CSP-S2020]贪吃蛇 题解
题意分析
给出一个序列,以权值为第一关键字,编号为第二关键字单调递增,每次操作让最大元素的权值减去最小元素的权值,删除最小元素,将最大元素插回序列并维护单调性,且每次操作要使当前最大元素操作后不必定被删除,求最后最少能剩下几个元素。
思路分析
设原序列为 $\{a_n\}$ 。为了方便表述,本文对序列 $\{a_n\}$ 中的元素的大小比较均指以权值为第一关键字,编号为第二关键字的比较。显然,这种比较不会出现相等的情况。其中,$val_i,num_i$ 分别表示元素 $i$ 的权值、编号。
分析后可以发现,操作终止时有两种情况。第一种就是只剩下一个元素,这个显然;第二种是当出现一个最大元素操作后会成为最小元素时。若这个最大元素成为最小元素后,下一个操作进行了,把它删了,那之前那个操作就不会进行;否则下一个操作就不会进行,同样终止。
第一种情况很好判断,主要分析第二种情况。根据上面的分析,只要有一个最大元素操作后会成为最小元素,操作就会终止。因此,在之前的操作中,最大元素操作后一定不会成为最小元素。
引理一:在出现最大元素操作后成为最小元素之前,操作后的元素存在单调性,即先操作的元素一定大于后操作的元素
简单证明:
先考虑权值均不等的情况。显然,设当前序列为 $a_1,a_2,a_3,...,a_{n-1},a_n$ ,则有 $val_1<val_2<val_3<...<val_{n-1}<val_n$ 。因为最大元素操作后不会成为最小元素,因此两次操作后的权值一定依次是 $val_n-val_1,val_{n-1}-val_2$ ,显然有 $val_n-val_1>val_{n-1}-val_2$ 。
再考虑权值出现相等的情况。根据题设条件有 $val_1\leq val_2\leq val_3\leq...\leq val_{n-1}\leq val_n$ 。若两次操作后的权值是相等的,即 $val_n-val_1=val_{n-1}-val_2$ ,那么一定有 $val_1=val_2,val_{n-1}=val_n$ ,因此有 $num_1<num_2,num_{n-1}<num_n$ ,那么先操作的 $a_n$ 仍然排在后操作的 $a_{n-1}$ 前面,不破坏单调性。
引理二:在出现最大元素操作后成为最小元素之前,一定是原序列 $\{a_n\}$ 从小到大被依次删除,即被删去的元素依次为 $a_1,a_2,a_3...$
简单证明:根据引理一,后操作的元素一定排在先操作的元素后面。因此,对于操作后不是最小元素的元素,下一次操作一定会有一个元素小于它而排在它后面,因此它一定不会成为最小的元素而被删除。而操作后的元素不会成为最小元素,因此被删除的元素一定是没操作过的元素,即原序列从小到大依次删除。
根据引理一,我们可以用一个队列维护操作后的元素,当前最大的元素就是原序列的最大元素和队头元素中更大的,即操作后和没操作的元素中更大的。
根据引理二,对于第 $i$ 次操作,被删除的最小元素即为 $a_i$ 。
综上,我们可以 $O(n)$ 模拟操作,直到出现最大元素操作后成为最小元素或只剩一个元素为止。
现在的问题是,当出现最大元素操作后会成为最小元素时,这个操作究竟进不进行。
根据上面的分析,若下一个操作会进行而将它删除,则这个操作不进行;若不会删除,则进行。而下一个操作为什么不会进行呢?就是因为下一个操作进行后下下个操作会将它删除。这样讲可能有点绕。按照原题意,即当前这条最大的蛇预判次大蛇不敢吃它,所以它敢把最小的蛇吃了而成为最小的蛇。
对于最大元素操作成为最小元素后,我们无法直接判断这个操作是不是一定进行。而当最大元素操作后不成为最小元素时,这个操作一定进行。因此,我们需要知道哪一次操作后最大元素不成为最小元素或只剩下一个元素,显然这个操作是一定进行的,即破坏了引理一中提到的单调性。
为什么单调性会被破坏呢?引理一的前提是最大元素操作后不会成为最小元素,此时被删除的元素的权值单调不降,因此存在单调性;而出现最大元素操作后成为最小元素后,被删除的元素的权值的单调性不存在了,因此操作后的元素的单调性也会被破坏。
可以想到模拟接下来的操作,直到出现操作后最大元素不成为最小元素或只剩下一个元素为止。
显然,在这些操作中,被删除的元素一定是上一次操作中的最大元素操作后的元素,可以直接用一个变量存储。而因为操作后的元素均为最小,那么其它元素不受影响,所以最大的元素只要在其它元素中找到最大的即可,具体方法和之前的相同。
在模拟这些操作时统计次数,若在第一个最大元素操作后成为最小元素的操作后第 $x$ 个操作必定进行,那么第 $x-1$ 个操作必定不进行,第 $x-2$ 个操作必定进行,以此类推。因此当 $x$ 为偶数时,当前操作进行,答案减 $1$ ;否则当前操作不进行。
综上,我们找到了一种 $O(n)$ 求解的方案。
具体操作
- 模拟操作,用一个队列维护操作后的元素,当前最大的元素为原序列的最大元素和队头元素中更大的,第 $i$ 次操作被删除的数为 $a_i$ 。当出现当前最大的元素操作后成为最小的元素,即小于 $a_{i+1}$ 时,进入第 $2$ 步操作;否则在只剩一个元素时停止模拟。
- 模拟操作,当前最大元素为原序列的最大元素和队头元素中更大的,最小元素即为上一次操作后的元素。当出现当前最大的元素操作后不是最小的元素或只剩两个元素时停止模拟。
//FJ-00445
//NOIP2020 RP++
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int N=1e6+100,INF=1e9;
struct Node
{
int val,num;
#define val(i) a[i].val
#define num(i) a[i].num
bool operator < (const Node y)
{
return val==y.val?num<y.num:val<y.val;
}
bool operator == (const Node y)
{
return val==y.val && num==y.num;
}
}a[N];
int T,n,k,ph,pb;
queue<Node> q;
Node blank={0,0},black={INF,INF};
Node Max(Node x,Node y)
{
return (x<y)?y:x;
}
Node Min(Node x,Node y)
{
return (x<y)?x:y;
}
bool query(Node la)//传入上一次操作后的元素
{
int cnt=0;
Node bk=Min(pb<ph?a[pb+1]:black,q.size()?q.back():black);//次小元素
while(1)
{
Node hd=Max(pb<ph?a[ph]:blank,q.size()?q.front():blank);//最大元素
if(a[ph]==hd)
ph--;
else
if(q.size())
q.pop();//维护指针和队列
if(hd==bk)
break;//只剩两个元素,即次小元素与最大元素相等
hd.val-=la.val;
if(bk<hd)
break;//当前最大的元素操作后不是最小的元素
la=hd;cnt++;//维护指针
}
return cnt&1;//在第 x 次操作终止,返回 x-1 的奇偶性
}
int main()
{
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
if(i==1)
{
scanf("%d",&n);
for(int j=1;j<=n;j++)
scanf("%d",&val(j)),num(j)=j;
}
else
{
scanf("%d",&k);
for(int j=1,x,y;j<=k;j++)
{
scanf("%d%d",&x,&y);
val(x)=y;
}
}
ph=n,pb=1;//分别为原序列最大元素和最小元素的指针
while(q.size())
q.pop();
while(pb<=ph)
{
Node x=Max((ph>pb)?a[ph]:blank,q.size()?q.front():blank);//最大元素
if(a[ph]==x)
ph--;
else
q.pop();//维护指针和队列
x.val-=val(pb);
if(x<a[pb+1] && n-pb>1)//当前最大的元素操作后是最小的元素且剩下的元素不止一个
{
if(query(x))//求解是否进行本次操作
pb++;
break;
}
q.push(x);pb++;//维护指针和队列
}
printf("%d\n",n-pb+1);
}
return 0;
}