1. 程式人生 > 其它 >題解 SP30906 【ADAUNIQ - Ada and Unique Vegetable】

題解 SP30906 【ADAUNIQ - Ada and Unique Vegetable】

\[\text{題目大意} \]

\(\quad\)你需要維護一個長度為 \(n\) 的序列,支援兩種操作。

  • 第一種操作為 \(1\) \(x\) \(y\),代表將第 \(x\) 個數修改為 \(y\)
  • 第二種操作為 \(2\) \(l\) \(r\),需要你求出在第 \(l\)\(r\) 個數中恰好出現了一次的數的數量。
  • \(n,a_i≤2 \times10^5\)
  • 本題的下標從 \(0\) 開始。(WA了一次就是這個原因)
\[\text{思路} \]

\(\quad\)本題就是帶修莫隊板子題,沒有學過的可以先做做P1903 [國家集訓隊]數顏色 / 維護佇列

\(\quad\)大體思路就是先儲存詢問和修改操作,分開來存,並記錄時間,之後將詢問排序。

  • 以詢問左邊界 \(l\) 為第一關鍵字,按塊的序號排序。
  • 以詢問右邊界 \(r\) 為第二關鍵字,按塊的序號排序。
  • 以時間 \(t\) 為第三關鍵字,按先後排序。

\(\quad\)之後依次處理詢問,記錄答案即可。

\(\quad\)值得一提的是塊的大小一般是 \(n^{ \frac{2}{3}}\),這樣時間複雜度理論上最簡,為 。

\(\quad\)還有什麼不懂的就看看程式碼吧!

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define re register int
#define il inline
using namespace std;
il int read()
{
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)&&ch!='-')ch=getchar();
  if(ch=='-')f=-1,ch=getchar();
  while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
  return x*f;
}
il void print(int x)
{
  if(x<0)putchar('-'),x=-x;
  if(x/10)print(x/10);
  putchar(x%10+'0');
}
const int N=2e5+5;
int n,m,blo,num,t1,t2,a[N],p[N],ans[N];
struct node{
	int l,r,id,tim;
}q[N];
struct nod{
	int id,c1,c2;
}c[N];
il bool cmp(node a,node b){
    if(a.l/blo!=b.l/blo)return a.l/blo<b.l/blo;//以詢問左邊界l為第一關鍵字,按塊的序號排序。
    if(a.r/blo!=b.r/blo)return a.r/blo<b.r/blo;//以詢問右邊界r為第二關鍵字,按塊的序號排序。
    return a.tim<b.tim;				//以時間t為第三關鍵字,按先後排序。
}
il void add(int x)
{
	if(p[x]==1)num--;
	p[x]++;
	if(p[x]==1)num++;
}
il void del(int x)
{
	if(p[x]==1)num--;
	p[x]--;
	if(p[x]==1)num++;
}
signed main()
{
	n=read();m=read();blo=pow(n,0.666);//blo表示塊長
	for(re i=1;i<=n;i++)a[i]=read();
	for(re i=1;i<=m;i++)
	{
		int op=read();
		if(op==2){
			q[++t1].l=read()+1;q[t1].r=read()+1;//題目所給的下標從0開始
			q[t1].id=t1;q[t1].tim=t2;//id用來儲存答案,tim為時間軸
		}
		else {
			c[++t2].id=read()+1;//題目所給的下標從0開始,id記錄修改的位置
			c[t2].c1=a[c[t2].id];//記錄之前的數字,方便撤銷
			a[c[t2].id]=c[t2].c2=read();//修改操作
		}
	}
	sort(q+1,q+t1+1,cmp);
	int l=1,r=0,t=t2;//因為前面輸入的時候把修改操作都做了一遍,所以此時時間t=t2
	for(re i=1;i<=t1;i++)
	{
       //依次調整l,r
		while(l<q[i].l)del(a[l++]);
		while(l>q[i].l)add(a[--l]);
		while(r<q[i].r)add(a[++r]);
		while(r>q[i].r)del(a[r--]);
		while(t<q[i].tim){//修改
			int x=c[++t].id;
			if(x>=l&&x<=r)del(a[x]);//只有x符合範圍才有可能修改答案
			a[x]=c[t].c2;
			if(x>=l&&x<=r)add(a[x]);
		}
		while(t>q[i].tim){//撤銷
			int x=c[t].id;
			if(x>=l&&x<=r)del(a[x]);
			a[x]=c[t--].c1;
			if(x>=l&&x<=r)add(a[x]);
		}
		ans[q[i].id]=num;
	}
	for(re i=1;i<=t1;i++)print(ans[i]),putchar('\n');
	return 0;
}