題目:codevs3333 高級打字機
【題目描述】
早苗入手了最新的高級打字機。最新款自然有着與以往不同的功能,那就是它具備撤銷功能,厲害吧。請爲這種高級打字機設計一個程序,支持如下3種操作:
1.T x:在文章末尾打下一個小寫字母x。(type操作)
2.U x:撤銷最後的x次修改操作。(Undo操作)(注意Query操作並不算修改操作)
3.Q x:詢問當前文章中第x個字母並輸出。(Query操作)
文章一開始可以視爲空串。
【輸入格式】
第1行:一個整數n,表示操作數量。
以下n行,每行一個命令。保證輸入的命令合法。
【輸出格式】
每行輸出一個字母,表示Query操作的答案。
【樣例輸入】
7
T a
T b
T c
Q 2
U 2
T c
Q 2
【樣例輸出】
b
c
【數據範圍】
對於40%的數據 n<=200;
對於100%的數據 n<=100000;保證Undo操作不會撤銷Undo操作。
<高級挑戰>
對於200%的數據 n<=100000;Undo操作可以撤銷Undo操作。
<IOI挑戰>
必須使用在線算法完成該題。
分析:
這道題很厲害,我還是第一次遇到有200%的數據的題,見識了,但是更讓我覺得厲害的是它的若干解法。
對於100%的數據模擬,以及<IOI挑戰>的若干在線算法(有什麼主席樹啦,trie樹+倍增啦,rope啦)網上的dalao們講得很不錯,這裏我只是理解一下針對高級挑戰O(n)的離線操作。真實原因是隻寫得來這個。。。
首先引用題解上的幾句話:(題解)
版本:接受第1--i個修改操作(包含Type和Undo)後的狀態稱爲版本i。版本0爲初始狀態。
版本鏈:一般的數據結構題目只有各種修改命令(比如本題的Type操作),那麼所有版本就會呈鏈狀排列。
這種情況下只需要設計一個合理的數據結構依次執行操作即可。
版本樹:Undo x撤銷最近的x次修改操作,實際上就是當前版本還原爲x次操作前的版本,換句話說,版本i = 版本i-x-1。
那麼有了題解後,我們對着題解分析:
首先,第i次操作(查詢不算操作)後得到版本i。若第i次操作爲Type,我們從版本i-1向版本i建一條邊,並將操作i添加的字符壓入棧中(回溯時退棧),表示通過這次Type我們能從版本i-1到版本i,並且當前版本爲棧中字符串。
若第i次操作爲Undo,那我們的版本i = 版本i-x-1,那麼我們便從版本i-x-1連一條邊想版本i,表示版本i其實是可以從版本i-x-1得到的。
對於每一操作後若有查詢,便在該操作上記錄一下這個查詢以及這查詢在所有查詢中是第幾次。
對於樣例我們可以建圖:
(圖醜,莫笑)
所以沿着圖dfs一次就可以了,由於會遍歷到x條邊(就是x個操作),而x是與n差不多的,所以複雜度爲O(n)
代碼:
#include<cstdio>
#include<vector>
using namespace std;
const int maxn=100010;
int n,q,x,p,act;
int h[maxn],nxt[maxn],to[maxn],ans[maxn];
char ch,a[maxn],st[maxn];
vector <int> ques[maxn];
void build(int u,int v) {to[++p]=v;nxt[p]=h[u];h[u]=p;}
void dfs(int pos,int top)
{
for(int i=h[pos];i;i=nxt[i]) dfs(to[i],top);//undo
if(a[pos]) st[top]=a[pos],dfs(pos+1,top+1);//type
for(int i=0;i<ques[pos].size();i++){//query
int v=ques[pos][i];
ans[v]=st[ans[v]];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",&ch);
switch (ch){
case 'T':scanf("%s",&a[act++]);break;
case 'U':{
scanf("%d",&x);act++;
build(act-x-1,act);
break;
}
case 'Q':{
scanf("%d",&x);
ques[act].push_back(++q);
ans[q]=x;
break;
}
}
}
dfs(0,1);
for(int i=1;i<=q;i++) printf("%c\n",ans[i]);
return 0;
}