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 命題手冊規定 每年由命題委員...