20200212 專題:后綴自動機(SAM)
總覽:
概述:
能解決的問題:
- 檢查字符串是否出現
- 不同子串個數
- 所有不同子串的總長度
- 字典序第 kk 大子串
- 最小循環移位
- 出現次數
- 第一次出現的位置
- 所有出現的位置
- 最短的沒有出現的字符串
- 兩個字符串的最長公共子串
- 多個字符串間的最長公共子串
時間和空間復雜度都是線性的!
對于長度為的字符串,最多個節點和條邊
維護信息:
通常為:
struct SAM{
int len,fa;
int nex[26];//如果不是小寫字母可用unordered_map
}tree[2*A];
為指向此節點的邊所代表的字符在數組中的位置(也是此節點代表的最長子串的長度)
()為出現次數比此節點維護的出現次數多的最長后綴
為子節點(類似AC自動機)
性質:
結束位置endpos:
對于一個子串,在原字符串中出現的結束位置的集合
如:對于,
每一個節點僅維護一類(即相同的子串在同一個節點中)
則一個節點中的子串短的必為長的后綴,且子串長度必為一段連續區間
構成有向無環圖(字符維護在邊上),構成一棵樹
在圖上,任意一條路徑代表的字符串為原字符串的子串
在樹上,祖宗節點為子節點的后綴,且在原字符串中的出現次數比子節點少(即為的祖宗,則)
所以節點和所對應的字符串的最大公共后綴為所對應的字符串
節點對應子串的出現次數為其所有子節點對應子串出現次數之和
每個節點所對應的子串數量為tree[u].len-tree[tree[u].fa].len
建立自動機:
在線算法,將節點一個一個加入。
步驟:
可手推!
模板:
inline void build(){
tree[0].len=0,tree[0].fa=-1;
las=tot=0;
memset(tree[0].nex,0,sizeof(tree[0].nex));//重復使用初始化
return;
}
inline void add(int x){
int now=++tot;
memset(tree[now].nex,0,sizeof(tree[now].nex));//重復使用初始化
tree[now].len=tree[las].len+1;
int p=las;
while(p!=-1&&!tree[p].nex[x]){
tree[p].nex[x]=now;
p=tree[p].fa;
}
if(p==-1) tree[now].fa=0;
else{
int q=tree[p].nex[x];
if(tree[q].len==tree[p].len+1) tree[now].fa=q;
else{
int cur=++tot;
tree[cur].len=tree[p].len+1;
tree[cur].fa=tree[q].fa;
for(int i=0;i<26;i++)
tree[cur].nex[i]=tree[q].nex[i];
while(p!=-1&&tree[p].nex[x]==q){
tree[p].nex[x]=cur;
p=tree[p].fa;
}
tree[now].fa=tree[q].fa=cur;
}
}
las=now;
return;
}
signed main(){
build();
for(int i=0;i<m;i++)
add(a[i]-'a');
return 0;
}
T1 P4070 [SDOI2016]生成魔咒
P4070 [SDOI2016]生成魔咒
Description
魔咒串由許多魔咒字符組成,魔咒字符可以用數字表示。例如可以將魔咒字符 1、2 拼湊起來形成一個魔咒串[1,2]。
一個魔咒串 S 的非空字串被稱為魔咒串 S 的生成魔咒。
例如 S=[1,2,1] 時,它的生成魔咒有 [1]、[2]、[1,2]、[2,1]、[1,2,1] 五種。S=[1,1,1] 時,它的生成魔咒有 [1]、[1,1]、[1,1,1] 三種。最初 S 為空串。共進行 n 次操作,每次操作是在 S 的結尾加入一個魔咒字符。每次操作后都需要求出,當前的魔咒串 S 共有多少種生成魔咒。
Input
第一行一個整數 。
第二行 個數,第 個數表示第 次操作加入的魔咒字符。
。用來表示魔咒字符的數字 滿足
Output
輸出 行,每行一個數。第 行的數表示第 次操作后 S 的生成魔咒數量
Sample Input
7
1 2 3 3 3 1 2
Sample Output
1
3
6
9
12
17
22
思路:
一句話題意:求不重復的子串數量
加入一個字符后,增加字符為末尾字符,對應節點,增加子串數量為,每次累加即可
代碼:
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
#define in Read()
#define int long long
inline int in{
int s=0,f=1;char x;
for(x=getchar();x<'0'||x>'9';x=getchar()) if(x=='-') f=-1;
for( ;x>='0'&&x<='9';x=getchar()) s=(s<<1)+(s<<3)+(x&15);
return f==1?s:-s;
}
const int A=1e5+5;
int n;
struct SAM{
int len,fa;
tr1::unordered_map <int,int> nex;
}tree[4*A];
int tot,last;
int ans=0;
inline void insert(int x){
int now=++tot;
tree[now].len=tree[last].len+1;
int p=last;
while(p!=-1&&!tree[p].nex.count(x)){
tree[p].nex[x]=now;
p=tree[p].fa;
}
if(p==-1) tree[now].fa=0;
else{
int q=tree[p].nex[x];
if(tree[p].len+1==tree[q].len) tree[now].fa=q;
else{
int cur=++tot;
tree[cur].len=tree[p].len+1;
tree[cur].fa=tree[q].fa;
tree[cur].nex=tree[q].nex;
while(p!=-1&&tree[p].nex[x]==q){
tree[p].nex[x]=cur;
p=tree[p].fa;
}
tree[q].fa=tree[now].fa=cur;
}
}
last=now;
ans+=tree[now].len-tree[tree[now].fa].len;
return;
}
inline void build(){
tree[0].len=0,tree[0].fa=-1;
last=0;
return;
}
signed main(){
n=in;
build();
while(n--){
int x=in;
insert(x);
printf("%lld\n",ans);
}
return 0;
}
T2 P3804 【模板】后綴自動機 (SAM)
P3804 【模板】后綴自動機 (SAM)
題目描述
給定一個只包含小寫字母的字符串。
請你求出 的所有出現次數不為 的子串的出現次數乘上該子串長度的最大值。
輸入格式
一行一個僅包含小寫字母的字符串
輸出格式
一個整數,為所求答案
輸入輸出樣例
輸入
abab
輸出
4
說明/提示
對于1的數據,
思路:
建立后綴自動機,在的樹上,葉節點對應子串的出現次數為,拓撲排序后出所有節點的次數。一個節點對應子串的出現次數相同,對應最長子串的長度為。掃描更新答案即可。
因為復雜度,所以拓撲排序不能用,可以桶排序:
int num[2*A],pos[2*A],cnt[2*A];
inline void toopsort(){
for(int i=1;i<=tot;i++) num[tree[i].len]++;
for(int i=1;i<=tot;i++) num[i]+=num[i-1];
for(int i=1;i<=tot;i++){
pos[num[tree[i].len]]=i;
num[tree[i].len]--;
}
for(int i=tot;i>0;i--)
cnt[tree[pos[i]].fa]+=cnt[pos[i]];
return;
}
代碼:
#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define LL long long
inline int in{
int s=0,f=1;char x;
for(x=getchar();x<'0'||x>'9';x=getchar()) if(x=='-') f=-1;
for( ;x>='0'&&x<='9';x=getchar()) s=(s<<1)+(s<<3)+(x&15);
return f==1?s:-s;
}
const int A=1e6+5;
char a[A];
int n;
struct SAM{
int len,fa;
int nex[26];
}tree[4*A];
int las,tot;
int cnt[2*A];
inline void build(){
tree[0].len=0,tree[0].fa=-1;
las=0;
return;
}
inline void add(int x){
int now=++tot;
tree[now].len=tree[las].len+1;
int p=las;
while(p!=-1&&!tree[p].nex[x]){
tree[p].nex[x]=now;
p=tree[p].fa;
}
if(p==-1) tree[now].fa=0;
else{
int q=tree[p].nex[x];
if(tree[p].len+1==tree[q].len) tree[now].fa=q;
else{
int cur=++tot;
tree[cur].len=tree[p].len+1;
tree[cur].fa=tree[q].fa;
for(int i=0;i<26;i++)
tree[cur].nex[i]=tree[q].nex[i];
while(p!=-1&&tree[p].nex[x]==q){
tree[p].nex[x]=cur;
p=tree[p].fa;
}
tree[q].fa=tree[now].fa=cur;
}
}
las=now;
cnt[now]=1;
return;
}
int num[2*A],pos[2*A];
inline LL get_ans(){
for(int i=1;i<=tot;i++) num[tree[i].len]++;
for(int i=1;i<=tot;i++) num[i]+=num[i-1];
for(int i=1;i<=tot;i++){
pos[num[tree[i].len]]=i;
num[tree[i].len]--;
}
for(int i=tot;i>0;i--)
cnt[tree[pos[i]].fa]+=cnt[pos[i]];
LL ans=0;
for(int i=1;i<=tot;i++)
if(cnt[i]!=1) ans=max(ans,(LL)cnt[i]*tree[i].len);
return ans;
}
signed main(){
scanf("%s",a+1);
n=strlen(a+1);
build();
for(int i=1;i<=n;i++)
add(a[i]-'a');
printf("%lld\n",get_ans());
return 0;
}
T3 P1368 工藝
P1368 工藝
題目描述
小敏和小燕是一對好朋友。
他們正在玩一種神奇的游戲,叫。
他們現在要做一個由方塊構成的長條工藝品。但是方塊現在是亂的,而且由于機器的要求,他們只能做到把這個工藝品最左邊的方塊放到最右邊。
他們想,在僅這一個操作下,最漂亮的工藝品能多漂亮。
兩個工藝品美觀的比較方法是,從頭開始比較,如果第i個位置上方塊不一樣那么誰的瑕疵度小,那么誰就更漂亮,如果一樣那么繼續比較第個方塊。如果全都一樣,那么這兩個工藝品就一樣漂亮。
輸入格式
第一行兩個整數,代表方塊的數目。
第二行個整數,每個整數按從左到右的順序輸出方塊瑕疵度的值。
輸出格式
一行n個整數,代表最美觀工藝品從左到右瑕疵度的值。
輸入輸出樣例
輸入
10
10 9 8 7 6 5 4 3 2 1
輸出
1 10 9 8 7 6 5 4 3 2
說明/提示
對于100%的數據,n<=300000
思路:
一句話題意:求字典序最小的同構序列
可以用最小表示法
將字符串拓展成兩倍,處理環,建立后綴自動機,從根節點DFS,貪心走最小字符,走次。
代碼:
#include<bits/stdc++.h>
using namespace std;
#define in Read()
inline int in{
int s=0,f=1;char x;
for(x=getchar();x<'0'||x>'9';x=getchar()) if(x=='-') f=-1;
for( ;x>='0'&&x<='9';x=getchar()) s=(s<<1)+(s<<3)+(x&15);
return f==1?s:-s;
}
const int A=6e5+5;
int n;
int a[A];
struct SAM{
int len,fa;
map <int,int> nex;
}tree[4*A];
int las,tot;
inline void build(){
tree[0].len=0,tree[0].fa=-1;
las=0;
return;
}
inline void add(int x){
int now=++tot;
tree[now].len=tree[las].len+1;
int p=las;
while(p!=-1&&!tree[p].nex.count(x)){
tree[p].nex[x]=now;
p=tree[p].fa;
}
if(p==-1) tree[now].fa=0;
else{
int q=tree[p].nex[x];
if(tree[p].len+1==tree[q].len) tree[now].fa=q;
else{
int cur=++tot;
tree[cur].len=tree[p].len+1;
tree[cur].fa=tree[q].fa;
tree[cur].nex=tree[q].nex;
while(p!=-1&&tree[p].nex[x]==q){
tree[p].nex[x]=cur;
p=tree[p].fa;
}
tree[now].fa=tree[q].fa=cur;
}
}
las=now;
return;
}
signed main(){
n=in;
for(int i=1;i<=n;i++)
a[i]=a[i+n]=in;
build();
for(int i=1;i<=2*n;i++)
add(a[i]);
int pos=0;
for(int i=1;i<=n;i++){
printf("%d ",tree[pos].nex.begin()->first);
pos=tree[pos].nex.begin()->second;
}
return 0;
}
T4 SP48 BEADS - Glass Beads
SP48 BEADS - Glass Beads
題目描述
求字典序最小的同構序列的第一個字符在原字符串中的位置
輸入格式: 輸入的第一行包含一個整數,表示輸入數據的組數。接著是組數據,每組數據包含一個字符串。每一組數據的長度不超過個字符。字符均為小寫的英語字母,它們的字典序按照英文字母表的順序確定。
輸出格式: 對于每一組數據,輸出一行,包含一個整數,代表最差的情況中首顆珠子的編號。這個答案滿足:字符串(代表分割情況)的字典序在所有的字符串中是最小的。如果答案不唯一,輸出最小的。
輸入輸出樣例
輸入
4
helloworld
amandamanda
dontcallmebfu
aaabaaa
輸出
10
11
6
5
思路:
同T3,但是要求位置。
為指向此節點的邊所代表的字符在數組中的位置,則輸出最終節點的(為兩倍字符串中取出的末位置)。
代碼:
#include<bits/stdc++.h>
using namespace std;
#define in Read()
inline int in{
int s=0,f=1;char x;
for(x=getchar();x<'0'||x>'9';x=getchar()) if(x=='-') f=-1;
for( ;x>='0'&&x<='9';x=getchar()) s=(s<<1)+(s<<3)+(x&15);
return f==1?s:-s;
}
const int A=1e5+5;
const int INF=1e9;
int n,m;
char a[2*A];
struct SAM{
int len,fa;
int nex[26];
}tree[2*A];
int las,tot;
int end[2*A];
inline void build(){
tree[0].len=0,tree[0].fa=-1;
las=tot=0;
memset(tree[0].nex,0,sizeof(tree[0].nex));
return;
}
inline void add(int x){
int now=++tot;
memset(tree[now].nex,0,sizeof(tree[now].nex));
tree[now].len=tree[las].len+1;
int p=las;
while(p!=-1&&!tree[p].nex[x]){
tree[p].nex[x]=now;
p=tree[p].fa;
}
if(p==-1) tree[now].fa=0;
else{
int q=tree[p].nex[x];
if(tree[q].len==tree[p].len+1) tree[now].fa=q;
else{
int cur=++tot;
tree[cur].len=tree[p].len+1;
tree[cur].fa=tree[q].fa;
for(int i=0;i<26;i++)
tree[cur].nex[i]=tree[q].nex[i];
while(p!=-1&&tree[p].nex[x]==q){
tree[p].nex[x]=cur;
p=tree[p].fa;
}
tree[now].fa=tree[q].fa=cur;
}
}
las=now;
return;
}
inline void DFS(int x,int dep){
if(dep==m){
printf("%d\n",tree[x].len-m+1);
return;
}
for(int i=0;i<26;i++)
if(tree[x].nex[i]){
DFS(tree[x].nex[i],dep+1);
break;
}
return;
}
signed main(){
n=in;
while(n--){
scanf("%s",a+1);
m=strlen(a+1);
build();
for(int i=1;i<=2*m;i++)
add(a[(i-1)%m+1]-'a');
DFS(0,0);
}
return 0;
}
智能推薦
Android中Paint、Canvas的基礎方法用法
首先paint是畫筆,可以根據paint中的方法設置畫筆的顏色、大小等等屬性,canvas是畫布,用paint畫筆可以在畫布上畫東西 代碼準備: canvas中的方法:(將下面講解的代碼分別放入注釋位置即可使用) 1、繪制單點: 方法:canvas.drawPoint(float x, float y, Paint mPaint); 參數:x:點的x軸位置;y:點的y軸位置;Paint:自定義畫筆...
十,Future設計模式
場景介紹 Future模式是多線程開發中非常常見的一種設計模式,它的核心思想是異步調用。這類似我們日常生活中的在線購物流程,帶在購物網看著一件商品時可以提交表單,當訂單完成后就可以在家里等待商品送貨上門。或者說更形象的是我們發送Ajax請求的時候,頁面是異步的進行后臺處理,用戶無需等待請求的結果,可以繼續瀏覽或操作其他內容。 參與角色 Future模式的主要角色有: Main:系統啟動,調用Fut...
小程序支付系列
小程序支付,涉及一些知識。 在微信提供的接口文檔中提供了一個微信支付接口,應該是直接調用這個接口就可以發起微信支付 文檔路徑:https://developers.weixin.qq.com/miniprogram/dev/api/api-pay.html#wxrequestpaymentobject 但是,當開始信...
兩個變量相乘_無廢話學編程基礎(C++篇)3: 變量,賦值語句
【現實需求】 今天我們要做一個小的應用程序,先說一說需求: 我們日常會去水果店買水果 例如, 蘋果5元/斤 香蕉12元/斤 橘子6元/斤 買完了水果要去結賬了,現在很少看著人手敲計算器了吧。 如果有一個小的應用程序可以解決這個問題不是更好嗎? 要完成這樣一個小的應用程序,需要知道以下幾個基本概念: 順序結構 變量 語句,賦值語句 輸入和輸出 順序結構 首先我們來看一下什么是程序的順序結構。 寫程序...
wifi放大器速度_放大器的速度有多快?
wifi放大器速度 AMP has caused quite the stir from a philosophical perspective, but the technology hasn’t received as close of a look. A few weeks ago, Ferdy Christant wrote about the unfair advantage...
猜你喜歡
Java IO講解(一)
Java IO講解 一、簡介 二、Java IO類庫基本架構 三、Java IO類型劃分 四、既然有了字節流,為什么還要有字符流? 五、字節字符相互轉換 一、簡介 IO(輸入輸出)問題是Web應用所面臨的的主要問題這一,因為在當前這個海量數據時代,數據在網絡中隨處流動。在這個數據流動的過程當中都涉及IO問題,大部分應用系統的瓶頸都是IO瓶頸。 二、Java IO類庫基本架構 ①基于字節操作的抽象類...
部署HPC集群的實施方案
部署HPC集群的實施方案 濟南友泉軟件有限公司 一、系統配置 1.1 網絡拓撲 1.2 操作系統 登錄節點:CentOS Linux release 7.3.1611 管理節點:CentOS Linux release 7.3.1611 計算節點:CentOS Linux release 7.9.2009, 二、計算節點、登錄節點配置 2.1 域名設置 在登錄節點、所有計算節點上執行以下命令,完成...
docker基本介紹&docker鏡像&docker常用命令
1.docker介紹 2.docker鏡像 3.docker常用管命令 4.dockerfile編寫和應用(真實企業應用) 5.docker中網絡部分講解 1.docker介紹 1.什么是docker Docker 是應用最廣泛的開源容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中 docker實質就像虛擬機一樣,就好像是一個具有獨立操作系統的真實機器 虛擬機是有真正的linux...
oracle ORA-01033
描述: 操作系統window10 ,Oracle 11g ,電腦異常斷電,再次打開電腦,連接oracle數據庫實例,報錯 Ora-01033 重啟Oracle各項服務,還是無法連接到數據庫 在查找了相關資料后,使用sqlplus命令,用system 登錄 輸入命令 SQL> shutdown normal SQL> startup mount SQL> alter databas...
vue+vant的Indexbar索引欄的內容加載問題
vant的IndexBar使用問題 按照官方的寫法引入 前端渣渣第一次使用vant的IndexBar時遇到的問題;根據公司的業務需求,需要做一個移動端的H5demo, 技術選型使用了vue+vant 按照官方的寫法引入 出現了如下錯誤: 只有標題,沒有內容 根據這個問題想出了解決思路: 參考文檔 vant官方文檔里面沒有引入cell這個組件 解決方法: 在這兩個地方加入Cell 這樣就解決了,va...