前言
這下又不是官解了吧?
模擬賽題,在一個月後又出現在了資料結構講稿上,令人忍俊不禁。
Solution
官方解法是用線段樹加矩陣,不過賽時的我顯然沒這麼聰明,是想不到的。
賽時我就只知道先發掘一些答案的性質。
一、一些性質
首先,發現這個撤銷操作比較棘手,考慮沒有撤銷操作的情況下,每一個新的操作對答案的貢獻。
發現:對於每次操作二,對答案的貢獻絕對只有 \(1\)!
只是在開頭結尾各自加上一個括號,從左括號 \(+1\)、右括號 \(-1\) 來看,字首陣列是時刻 \(\ge0\) 的,如果在開頭結尾加上一個括號,那字首陣列就只有最後一位為 \(0\),也就是以第一個括號開頭只能匹配結尾括號,反之亦然。
類似的,考慮操作一在結尾加上一組括號,增加的貢獻一定是以最後括號結尾的,此時
(......)(......)(......) + ()
前面有幾組括號貢獻就是組數 \(+1\),例如上圖中尾部加入括號,貢獻為 \(4\)。
換句話說,操作二貢獻恆為 \(1\),操作一貢獻取決於前面連續操作一的次數,貢獻為連續次數 \(+1\)。
有一種 OSU! 的美。
二、如何維護
你以為這道題就做完了?撤銷操作才是本題的重點!
考慮撤銷操作可能會對連續操作一的長度有影響。
比如說:
2 1 1 1 1 2 1 1 1
撤銷掉第二個操作二,會導致其左右兩邊操作一連起來。
就像打音遊,本來某個位置一直斷,有一次這裡全連了,最後連擊數就會變高。
而且還可以撤銷撤銷操作,就導致已經被撤銷的操作二可能會打贏復活賽。
換句話說我們得維護一個關於操作二的連結串列,能支援隨時在某個位置插入,刪除和查詢前面或者後面的第一個操作二。
但是直接寫連結串列,操作一怎麼辦?
因為我們還需要能夠快速得出兩個位置之間的操作一次數,這是一個區間查詢,連結串列上顯然行不通。
但是偶然間想到一個複雜度稍遜但是也可以維護連結串列的東西:平衡樹!
這道題的解法就顯然了:
用 set 維護操作二的插入和刪除,記錄下每次操作二的位置之後,便可以用樹狀陣列來查詢其間操作一數量。
三、具體影響
末尾加操作二,簡單,\(+1\) 即可。
末尾加操作一,用 set 找到上一個還活著的操作二,樹狀陣列求出其間的操作一數量為 \(x\) 貢獻為 \(x+1\)。
中間插入或刪除操作一:找到左右的操作二,剩下同上。
中間插入或刪除操作二,看看這個操作二左右兩端各有多長的操作一,簡單計算即可。
複雜度顯然為 \(O(n\log n)\)。
這顯然不是官方解法,而且顯然比官方做法簡單。
AC 程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t[1234657],n,cnt;
void change(int x,int v)
{
for(;x<=n+10;x+=x&-x) t[x]+=v;
}
int ask(int x)
{
int ans=0;
for(;x>0;x-=x&-x) ans+=t[x];
return ans;
}
set<int> s,fs;
int i,j,op,x;
int ot[666666],ch[666666],lo[666666];
signed main()
{
cin>>n;
for(i=1;i<=n;i++)
ch[i]=i;
int ans=1,p=1;
s.insert(0);fs.insert(0);
s.insert(n+1);fs.insert(-n-1);
for(i=1;i<=n;i++)
{
cin>>op;
ot[i]=op;
if(op==1)
{
cnt++;
lo[i]=cnt;
change(cnt,1);
int q=-(*fs.upper_bound(-cnt));
ans+=ask(cnt)-ask(q)+1;
}
if(op==2)
{
ans++;
cnt++;
lo[i]=cnt;
s.insert(cnt);fs.insert(-cnt);
}
if(op==3)
{
cin>>x;
ch[i]=-ch[x];
if(ch[i]>0)
{
int ty=ot[ch[i]],loc=lo[ch[i]];
int fr=-(*fs.upper_bound(-loc)),bk=*s.upper_bound(loc);
if(ty==1)
{
change(loc,1);
ans+=ask(bk-1)-ask(fr)+1;
}
if(ty==2)
{
s.insert(loc);fs.insert(-loc);
ans++;
int c=ask(bk-1)-ask(fr);
ans-=c*(c+3)/2;
c=ask(loc-1)-ask(fr);
ans+=c*(c+3)/2;
c=ask(bk-1)-ask(loc);
ans+=c*(c+3)/2;
}
}
else
{
int ty=ot[-ch[i]],loc=lo[-ch[i]];
int fr=-*fs.upper_bound(-loc),bk=*s.upper_bound(loc);
if(ty==1)
{
change(loc,-1);
ans-=ask(bk-1)-ask(fr)+2;
}
if(ty==2)
{
ans--;
s.erase(loc);fs.erase(-loc);
int c=ask(bk-1)-ask(fr);
ans+=c*(c+3)/2;
c=ask(loc-1)-ask(fr);
ans-=c*(c+3)/2;
c=ask(bk-1)-ask(loc);
ans-=c*(c+3)/2;
}
}
}
cout<<ans<<endl;
}
return 0;
}
後記
這題解有一種為了一碗醋包了一盤餃子的美。
什麼叫資料結構只會樹狀陣列啊。