1. 程式人生 > >【題解】JSOI2008 最大數

【題解】JSOI2008 最大數

超級 格式 應該 之前 tps long ostream 表達 freopen


  • 題目描述

現在請求你維護一個數列,要求提供以下兩種操作:

  1. 查詢操作。
  • 語法:Q L

    功能:查詢當前數列中末尾L個數中的最大的數,並輸出這個數的值。

    限制:L不超過當前數列的長度。(L>=0)

  1. 插入操作。
  • 語法:A n

    • 功能:將n加上t,其中t是最近一次查詢操作的答案(如果還未執行過查 詢操作,則t=0),並將所得結果對一個固定的常數D取模,將所得答案插 入到數列的末尾。

    • 限制:n是整數(可能為負數)並且在長整範圍內。

    • 註意:初始時數列是空的,沒有一個數。

  • 輸入輸出格式

  1. 輸入格式:
    第一行兩個整數,M和D,其中M表示操作的個數(M <= 200,000),D如上文中所述,滿足(0<D<2,000,000,000)

接下來的M行,每行一個字符串,描述一個具體的操作。語法如上文所述。

  1. 輸出格式:
    對於每一個查詢操作,你應該按照順序依次輸出結果,每個結果占一行。
  • 輸入輸出樣例
  1. 輸入樣例#1:

5 100

A 96

Q 1

A 97

Q 1

Q 2

  1. 輸出樣例#1:

96

93

96


  • 前言:

本蒟蒻很早以前就在做這道題了,當時是在搞線段樹時搜到了這道題,第一眼看到這題我根本不覺得它與線段樹有什麽關系,於是就隨便寫了一個二分+偽單調棧,結果連樣例都過不了。。。最近又搞起了線段樹,先是看了一下黃學長的博客知道自己之前的單調棧錯在了哪裏,然後又搞了一棵線段樹,結果玄學般全部MLE???為什麽最近做題總會遇到玄學錯誤,於是學習黃學長方法,搞個數組記錄l和r就過了。


  • 單調棧超級詳解:

推薦先搞懂這道題的手寫隊列方法:滑動窗口

我們搞一個數組記錄加的每一個數。在我看來,這個數組其實不能叫做棧,另一篇題解這裏可能有點表達不太準確(個人觀點),然後我們再搞一個數組,我們可以理解為將這個數組儲存一個指針,這才是一個單調棧,因為它記錄的元素從左到右是依次遞減的。我們搞一個tail記錄棧頂位置,當一個新數加進來時,我們將它與棧頂的元素做比較,如果棧頂的元素都比他小,就不斷tail--,知道刪去所有指針或找到一個比它更大的數,則停止操作,將tail++的地方存放這個新數的位置(指針)。大家可以想一想,它是要從後往前找最大的數,如果在後面已經有了一個比前面都大的數,那前面記錄的指針都可以不要了,因為從這個數倒數所有查詢的答案都是它。

然後就是查詢了,查詢我們用二分查找查到我們要的位置,有人若是認真研究了我的代碼會發現這個二分查找其實就是個lower_bound,返回大於等於這個數的第一個位置,所以它返回的就是我們需要的下標。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#define ll long long 
using namespace std;
const int maxn=200010;
ll sta[maxn];
ll m,d,t=0;
int node[maxn],len=0,tail=0;
int binary_search(int x){
    int l=1,r=tail;
    int mid;
    while(l<=r){
       mid=(l+r)>>1;
        if(node[mid]>x)r=mid-1;
        else if(node[mid]==x)return mid;
        else l=mid+1;
    }
}
int main()
{
    char opt[15];
    freopen("bzoj_1012.in","r",stdin);
    freopen("bzoj_1012.out","w",stdout);
    cin>>m>>d;
    while(m--){
        scanf("%s",opt); 
        if(opt[0]==‘A‘){
          ll x;
          scanf("%lld",&x);
          x=(x+t)%d;
          sta[++len]=x;
          while(tail&&sta[node[tail]]<=x)tail--;
          node[++tail]=len;
        }
        else{
          int l;
          scanf("%d",&l);
          if(l==0){
            cout<<0<<endl;
            t=0;
            continue;
          }
          int x=binary_search(len+1-l);
          cout<<sta[node[x]]<<endl;
          t=sta[node[x]];   
        }
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

  • 線段樹做fa

有人說線段樹跑得很慢,為什麽我這裏跑得比單調棧還快???難道因為是黃 學長的玄學技巧??
我也搞不懂之前的代碼為何全部玄學MLE,就當這種新方法一種技巧吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define ll long long 
using namespace std;
const int maxn=200000;
ll maxx[maxn<<2]; 
int cnt=0;
int L,R,tar;
ll  m,d,t=0;
int le[maxn<<2],ri[maxn<<2];
void build(int now,int l,int r)
{
    le[now]=l,ri[now]=r;
    if(l==r){
        return ;
    } 
    
    int mid=(l+r)>>1;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    return ;
}
ll query(int now){
    int l=le[now],r=ri[now]; 
    if(L<=l&&r<=R){
        return maxx[now];
    }
    int mid=(l+r)>>1;
    ll ans=0;
    if(L<=mid)ans=max(ans,query(now<<1));
    if(R>mid)ans=max(ans,query(now<<1|1));
    return ans;
}
void insert(int now,ll x)
{
    int l=le[now],r=ri[now];
    if(l==r){
        maxx[now]=x;
        return ;
    }
    
    int mid=(l+r)>>1;
    if(tar<=mid)insert(now<<1,x);
    if(mid<tar)insert(now<<1|1,x);
    maxx[now]=max(maxx[now<<1],maxx[now<<1|1]);
    return ;
}
int main()
{
    char opt[15];
    cin>>m>>d;
    build(1,1,m);
    while(m--)
    {
        scanf("%s",opt);
        if(opt[0]==‘A‘){
            ll x;
            scanf("%lld",&x);
            tar=++cnt;
            insert(1,(x+t)%d);
        }
        else{
            int l;
            scanf("%d",&l);
            if(l!=0)
            { 
            L=cnt-l+1,R=cnt;
            t=query(1);
            }
            else t=0;
            cout<<t<<endl; 
        }
    }
    return 0;
}

【題解】JSOI2008 最大數