NOI2018 你的名字

2022-02-05 10:33:29 字數 3802 閱讀 3847

sam寫的太不熟練了~~sam上的線段樹合併也不熟練~~~

調了半天樣例

給定乙個s,q次詢問,每次給出t,l,r,

求對於s[l,r],屬於t的子串卻不屬於s[l,r]的子串有多少個

看上去挺簡潔的乙個問題。。。

對於s[1,n]68pts?

如果做過

[heoi2015]最短不公共子串

就好做多了!

可以對a,b分別建sam

拓撲排序找到a中每個點的後面路徑條數。

然後在a上面匹配一遍,如果b匹配不出,直接加上a後面的路徑條數

剛才的暴力方法實際上不適用了

因為dag根本無法精確找到[l,r]的部分。。

換乙個角度

不從圖的路徑角度考慮子串了

直接從子串定義考慮

考慮,對於t,[1,i]這個字首貢獻的答案

假設同乙個子串可以算多次的話

把[1,i]這個字首在s[l,r]中匹配,設最長長度是mx

那麼貢獻的答案就是i-mx

怎麼計算"把[1,i]這個字首在s[l,r]中匹配"得到的最長字尾長度?

用線段樹合併維護s的sam中,點p的right集合

設[1,i-1]匹配的長度為now,匹配在sam上的點為p

如果p有c出點,出點是x

如果x的right集合中有[l+now,r]區間中乙個元素,意味著可以直接匹配下去,得到最長的長度了。break

否則now--,繼續嘗試。如果now==len[fa[p]],可以更新到更大的集合了,p=fa[p]

設i字首匹配長度為lim[i]

upda:2019.3.8:

這個匹配本質上是不斷找到當前可能的最長字尾now+'c'在s中所有出現位置,然後看這些出現位置有沒有末尾在[l+now,r]的

至於相同的子串是1個

那麼對t串再建立sam,用parent樹去重,parent樹上dfs,每個點的貢獻是max(0,min(len[x]-len[fa[x]],len[x]-lim[x]))

相當於把同構的串放在一起,只計算一次

注意,1.線段樹合併還要支援之後的查詢

所以必須每次新建節點

類似:cf666e forensic examination

2.tot,cnt,num計數器很多別混(懶得namespace了)

#include#define reg register int

#define il inline

#define mid ((l+r)>>1)

#define numb (ch^'0')

using

namespace

std;

typedef

long

long

ll;il

void rd(int &x)

namespace

miracle

struct

trt[n*20

];

intrt[n];

inttot;

void pushup(int

x)

void upda(int &x,int l,int r,int

to)

if(to<=mid) upda(t[x].ls,l,mid,to);

else upda(t[x].rs,mid+1

,r,to);

pushup(x);

}int merge(int x,int y,int l,int

r) t[id].ls=merge(t[x].ls,t[y].ls,l,mid);

t[id].rs=merge(t[x].rs,t[y].rs,mid+1

,r);

pushup(id);

return

id; }

void ins(int c,int

l)

int q=ch[p][c];

if(len[q]==len[p]+1

) len[++cnt]=len[p]+1

; fa[cnt]=fa[q];fa[q]=fa[nd]=cnt;

for(reg j=0;j<26;++j) ch[cnt][j]=ch[q][j];

for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=cnt;

}struct

edgee[

2*n];

int hd[2*n],num;

void add(int x,int

y)

void

build()

}void dfs(int

x) }

int query(int x,int l,int r,int l,int

r)}sam;

struct

samsam

void ins(int c,int

l)

int q=ch[p][c];

if(len[q]==len[p]+1

) len[++cnt]=len[p]+1

; fa[cnt]=fa[q];fa[q]=fa[nd]=cnt;

for(reg j=0;j<26;++j) ch[cnt][j]=ch[q][j];

for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=cnt;

}struct

edgee[

2*n];

int hd[2*n],num;

void add(int x,int

y)

void

build()

}void dfs(int x)

ans+=max(0,min(len[x]-mx[x],len[x]-len[fa[x]]));

}void

clear()

mx[i]=0;len[i]=0

; hd[i]=0

; fa[i]=0

; }

num=0

; cnt=1

; }

}sam;

void

clear()

intmain()

//cout<<" after ins "//cout<<" after build ");//

cout<<" after dfs "intl,r;

while(q--)

if(!now) break

; --now;

if(now==sam.len[sam.fa[p]]) p=sam.fa[p];

}lim[i]=now;

//cout<<" lim "<}

sam.init();

for(reg i=1;i<=len;++i)

ans=0

; sam.build();

sam.dfs(1);

printf(

"%lld\n

",ans);

}return0;

}}signed main()

/*author: *miracle*

date: 2019/1/18 17:48:14

*/

總結:sam對於公共子串問題乙個基本的方法是跑上去匹配

然後下來再考慮每個位置的貢獻

parent樹、dag圖無形對子串進行了同構的去重

NOI2018 你的名字

題目描述 小 a 被選為了 ion2018 的出題人,他精心準備了一道質量十分高的題目,且已經把除了題目命名以外的工作都做好了。由於 ion 已經舉辦了很多屆,所以在題目命名上也是有規定的,ion 命題手冊規定 每年由命題委員會規定乙個小寫字母字串,我們稱之為那一年的命名串,要求每道 題的名字必須是...

NOI2018 你的名字

嘟嘟嘟 這題以前寫過棄掉了,後來竟然連自己的68分寫法都看不懂了 這次回首這道題,心想怎麼說也得把這題切了,哪怕抄題解也行。但沒想到別人的題解自己怎麼也看不懂,最終還是自己搞出來了 我真nb 總用時前一天下午到第二天凌晨0 30 第二天半個上午。我們先來回顧 l 1,r n 的情況。大體思路就是求出...

NOI2018 你的名字

實力強大的小 a 被選為了 ion2018 的出題人,現在他需要解決題目的命名問題。小 a 被選為了 ion2018 的出題人,他精心準備了一道質量十分高的題目,且已經把除了題目命名以外的工作都做好了。由於 ion 已經舉辦了很多屆,所以在題目命名上也是有規定的,ion 命題手冊規定 每年由命題委員...