火星人的研究 Prefix

2021-06-16 08:35:17 字數 4034 閱讀 4655

【題意】

對乙個字串實現三種操作

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 火星人

人類終於登上了火星的土地並且見到了神秘的火星人。人類和火星人都無法理解對方的語言,但是我們的科學家發明了一種用數字交流的方法。這種交流方法是這樣的,首先,火星人把乙個非常大的數字告訴人類科學家,科學家破解這個數字的含義後,再把乙個很小的數字加到這個大數上面,把結果告訴火星人,作為人類的回答。火星人用...