1. 程式人生 > >洛谷 P3709 大爺的字串題 解題報告

洛谷 P3709 大爺的字串題 解題報告

題目描述

給你一個字串a,每次詢問一段區間的貢獻

貢獻定義:

每次從這個區間中隨機拿出一個字元x,然後把x從這個區間中刪除,你要維護一個集合S

如果S為空,你rp減1

如果S中有一個元素不小於x,則你rp減1,清空S

之後將x插入S

由於你是大爺,平時做過的題考試都會考到,所以每次詢問你搞完這段區間的字元之後最多還有多少rp?rp初始為0

詢問之間不互相影響~

輸入輸出格式

輸入格式:

第一行兩個數n,m,表示字串長度與詢問次數

之後一行n個數,表示字串

由於你是大爺,所以字符集1e9

之後m行每行兩個數,表示詢問的左右區間

輸出格式:

m行,每行一個數表示答案

輸入輸出樣例

輸入樣例#1:
3 3
3 3 3
3 3
3 3
3 3
輸出樣例#1:  複製
-1
-1
-1

藉著此題複習一下莫隊

首先分析一下題目:

要求減去的RP最少,那麼就是求最少可以將數列分成幾段單調上升子序列。

單調上升子序列的個數,就是最後的答案。

如果所有數字均不重複,那麼答案等於1

有一個數字重複,那麼就是2

因此,答案就等於眾數的出現次數。

因為ai最大為109,所以要先對a陣列離散化。(因為只是求眾數出現次數,故離散化後無任何影響)

統計一個數字出現次數cnt,再統計一下這個數字出現次數的出現次數(形容的比較抽象,看下面的解釋/程式碼)。

  • add操作

  只要這個數字出現次數等於當前ans,那麼ans++。

  • del操作

  當前數字出現次數等於ans,並且這個數字的數量的出現次數為1,即沒有別的數字出現次數等於ans,那麼ans--


程式碼如下:

#include <bits/stdc++.h>
using namespace std;

inline void read(int &a) {a=0;int b=1,c=getchar(); while(c!='-'&&(c>'
9'||c<'0')) c=getchar(); if(c=='-') b=-1,c=getchar(); while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar(); a*=b; } const int N=2e5+5; int n,m,a[N],block,b[N],cnt[N],rec[N],ans,zs[N]; struct sq { int l,r,id; }q[N]; inline bool cmp(sq a,sq b) { return (a.r/block)==(b.r/block)?a.l<b.l:a.r<b.r; } inline void add(int x) { if(cnt[a[x]]==ans) ++ans; zs[cnt[a[x]]]--; zs[++cnt[a[x]]]++; } inline void del(int x) { if(ans==cnt[a[x]]&&zs[cnt[a[x]]]==1) --ans; zs[cnt[a[x]]]--; zs[--cnt[a[x]]]++; } int main() { read(n);read(m);block=sqrt(n); for(int i=1;i<=n;i++) { read(a[i]); b[i]=a[i]; } sort(b+1,b+1+n); int len=unique(b+1,b+1+n)-b-1; for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+len,a[i])-b; for(int i=1;i<=m;i++) {read(q[i].l);read(q[i].r); q[i].id=i; } sort(q+1,q+1+m,cmp); int l=1,r=0; for(int i=1,ql,qr,qi;i<=m;i++) { ql=q[i].l,qr=q[i].r,qi=q[i].id; while(l<ql) del(l++); while(l>ql) add(--l); while(r<qr) add(++r); while(r>qr) del(r--); rec[qi]=ans; } for(int i=1;i<=m;i++) cout<<-rec[i]<<'\n'; return 0; }
View Code