【題意】
對乙個字串實現三種操作
q i j:詢問該字串的字尾i和字尾j的最長公共字首長度
r i c:將當前字串第i位變為小寫字母c
i i c:在當前字串第i位後插入乙個小寫字母c
【輸入】
輸入檔案第一行為乙個字串(僅包含小寫字母)。
接下來的一行的整數q 代表操作的個數(1 ≤q ≤15000)。
接下來q 行表示了待執行的操作。
q i j 表示求lcp(i, j)的數值
r i char 表示將第i 位置的字元替換成小寫字母char
i i char 表示在第i 個字元後插入小寫字母char
【輸出】
對於每乙個q i j,輸出一行為lcp(i, j)的數值。
最初看到這道題在想是不是對於修改可以通過修改字尾陣列實現
發現不可行
ac做法是hash
求最長公共字首可以通過二分答案來變為存在性問題
那麼只要比較二者的hash值即可
雖然有一定概率不同的字串hash為相同的值,但是只要方程選取的當出錯的機率還是很小的
那麼問題就轉換為了如何快速求乙個字串子串的hash值
採用的hash方程為長度為n的字串的hash值為(a[1]*k^0+a[2]*k^1+a[3]*k^2..+a[n]*k^(n-1)) mod maxn
如果用這個方程的話,那麼一段的hash可以通過兩段直通末尾的字串hash值相減得出
我才用了splay樹來維護快速查詢
對於每個字元將其放入樹中,其左子樹中的字元都是比其位置靠前的,其右子樹中的字元都是比其位置靠後的
每個節點的權值h為其子樹(包括本身)構成的原字串的子串的hash值,並記錄子樹的節點總數sum
那麼詢問原字串乙個字尾的hash值只需要將他所在的節點上移到根節點處,這時他(它本身代表的字元序號+(其右子樹權值*k))即為這個字尾的hash值
對於變換,只要將改變的節點移到根節點之後修改hash值即可
對於插入,將要插入位置的前乙個移到根節點後插到右子樹即可
program prefix;
const
maxn=9875321;
k=27;
var tot,count,a,b,ans,q,n,i,j:longint;
s:ansistring;
ch,order,space:char;
h:array [0..100011] of int64;
power:array [0..100011] of int64;
nam,father,left,right,sum:array [0..200011] of longint;
procedure update (now:longint);inline;
begin
h[now]:=(h[left[now]]+power[sum[left[now]]]*nam[now]
+power[sum[left[now]]+1]*h[right[now]]) mod maxn;
end;
procedure build (l,r,mother:longint);
var now:longint;
begin
if l>r then exit;
now:=(l+r) div 2;
nam[now]:=ord(s[now])-96;
sum[now]:=r-l+1;
if now>mother then right[mother]:=now
else left[mother]:=now;
father[now]:=mother;
build(l,now-1,now);
build(now+1,r,now);
update(now);
end;
function find (lef,now:longint):longint;
begin
if sum[left[now]]+1=lef then exit(now)
else
if sum[left[now]]+10 do rotate(i,father[i]);
nam[i]:=ord(what)-96;
update(i);
end;
procedure ins (who:longint;what:char);
var i:longint;
begin
i:=find(who,right[0]);
while father[i]<>0 do rotate(i,father[i]);
inc(n);
nam[n]:=ord(what)-96;
right[n]:=right[i];
father[right[i]]:=n;
father[n]:=i;
right[i]:=n;
inc(sum[i]);
sum[n]:=sum[right[n]]+1;
update(n);
update(i);
end;
function hash (a,b:longint):longint;
var i,j:int64;
begin
i:=find(a,right[0]);
while father[i]<>0 do rotate(i,father[i]);
i:=nam[i]+k*h[right[i]];
if b>n then j:=0
else
begin
j:=find(b,right[0]);
while father[j]<>0 do rotate(j,father[j]);
j:=nam[j]+k*h[right[j]];
end;
exit(((i-j*power[b-a] mod maxn) mod maxn + maxn) mod maxn);
end;
function lcq (a,b:longint):longint;
var i,j,l,r,mid:longint;
begin
if nam[find(a,right[0])]<>nam[find(b,right[0])] then exit(0);
l:=1;
if n-a+1r do
begin
mid:=l+r-(l+r) div 2;
if hash(a,a+mid)=hash(b,b+mid) then l:=mid
else r:=mid-1;
end;
exit(l);
end;
begin
assign(input,'prefix.in');
reset(input);
assign(output,'prefix.out');
rewrite(output);
readln(s);
n:=length(s);
power[0]:=1;
for i:=1 to 100010 do
power[i]:=power[i-1]*k mod maxn;
build(1,n,0);
readln(q);
for i:=1 to q do
begin
read(order);
case order of
'q':begin
read(a,b);
writeln(lcq(a,b));
end;
'r':begin
read(a,space,ch);
rep(a,ch);
end;
'i':begin
read(a,space,ch);
ins(a,ch);
end;
end;
readln;
end;
close(input);
close(output);
end.
codeves天梯 火星人
人類終於登上了火星的土地並且見到了神秘的火星人。人類和火星人都無法理解對方的語言,但是我們的科學家發明了一種用數字交流的方法。這種交流方法是這樣的,首先,火星人把乙個非常大的數字告訴人類科學家,科學家破解這個數字的含義後,再把乙個很小的數字加到這個大數上面,把結果告訴火星人,作為人類的回答。火星人用...
火星人計數法
問題描述 火星人用一種非常簡單的方式來表示數字 掰手指。火星人只有乙隻手,但這只手上有成千上萬的手指,這些手指排成一列,分別編號為1,2,3 火星人的任意兩根手指都能隨意交換位置,他們就是通過這方法計數的。乙個火星人用乙個人類的手演示了如何用手指計數。如果把五根手指 拇指 食指 中指 無名指和小指分...
洛谷1088 火星人
人類終於登上了火星的土地並且見到了神秘的火星人。人類和火星人都無法理解對方的語言,但是我們的科學家發明了一種用數字交流的方法。這種交流方法是這樣的,首先,火星人把乙個非常大的數字告訴人類科學家,科學家破解這個數字的含義後,再把乙個很小的數字加到這個大數上面,把結果告訴火星人,作為人類的回答。火星人用...