[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)$ 求解的方案。

具體操作

  1. 模擬操作,用一個隊列維護操作后的元素,當前最大的元素為原序列的最大元素和隊頭元素中更大的,第 $i$ 次操作被刪除的數為 $a_i$ 。當出現當前最大的元素操作后成為最小的元素,即小於 $a_{i+1}$ 時,進入第 $2$ 步操作;否則在只剩一個元素時停止模擬。
  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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM