Manacher例題問題彙總

2022-05-31 14:57:06 字數 4715 閱讀 1660

本篇隨筆面向個人

本來以為回文串很簡單,但是沒有做對應的練習前下此定論為時過早。

雖然例題中也沒有模板題(因為太簡短了……),但是有必要預先打一遍。

將原字串頭部插入$,尾部插入@\0,再將間隔中插入#(包括$後和@前也要有#);比如說如果原串為abc,則變換為$#a#b#c#@

定義變數mx,p,r[i]。

遍歷新字串。首先確定當前r[i]的下界為\(i,接著嘗試暴力向兩邊拓展,最後看看能不能更新\(mx\),若能則同時更新\(p\)。

void manacher()

=p\),所以\(j=2*p-i\)。

預處理可以用一句話解決

s[0]='$';scanf("%d %s",&n,s+1);for(int i=n;i>=1;i--) s[2*i]=s[i],s[2*i+1]='#';s[1]='#';

這同時也說明了char相對於string的優點之一。

給乙個字串\(s\),問其有多少對不相交的回文子串。

先跑模板,計算每個位置上最長回文串。再差分轉字首和地求以i開頭/結尾的回文串個數,再對其中乙個做字首和,用另乙個乘一下即為答案。

#include#include#include#include#include#include#include#include#include#define il inline

#define re register

#define ll long long

#define ull unsigned long long

#ifdef th

#define debug printf("now is %d\n",__line__);

#else

#define debug

#endif

using namespace std;

templateinline void read(t&x)

x*=fu;

}inline int read()

return x*fu;

}int g[55];

templateinline void write(t x)

while(x);

for(int i=g;i>=1;--i)putchar('0'+g[i]);putchar('\n');

}string str,t;

ll r[300010],pre[300010],suf[300010],sum[300010],ans;

void init()

for(int i=len;i>=1;i--)

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

ans=0;

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

cout《題面亦可見洛谷 p4287 [shoi2011]雙倍回文

在跑manacher的更新答案時,同時看看當前回文串的前一部分的字尾是否也是乙個回文串。此判定可以用之前計算過的\(r[i]\)判定。

#include#include#include#include#include#include#include#include#include#define il inline

#define re register

#define ll long long

#define ull unsigned long long

#ifdef th

#define debug printf("now is %d\n",__line__);

#else

#define debug

#endif

using namespace std;

templateinline void read(t&x)

x*=fu;

}inline int read()

return x*fu;

}int g[55];

templateinline void write(t x)

while(x);

for(int i=g;i>=1;--i)putchar('0'+g[i]);putchar('\n');

}int n;

int len;

char ch[1000010];

string t;

int r[1000010];

int p=0,mx=0,ans;

int main()

}mx=i+r[i];

p=i;

} }write(ans);

return 0;

}

在題解中也遇到了說可以用並查集,回文自動機(pam)的,日後了解。

很容易想到manacher,先打乙個板子。處理完以i為中心的最長回文串之後就不知道該做什麼了。

想起例題,在維護\(r[i]\)的同時再維護\(suf[i]\)和\(pre[i]\)……彷彿這裡有點意思。

在計算\(r[i]\)時同時維護\(ll[i],rr[i]\),分別表示以i為終點/起點的最長回文子串。

(然後其實這裡可以用線段樹維護,但會多乙個\(\log\)||\(10^5\)誰會管多不多個\(\log\)呢||算了考慮各方面還是用manacher吧)

首先在計算每乙個\(r[i]\)時,只更新最長子序列的左右端點,即\(ll[i+r[i]-1],rr[i-r[i]+1]\)

注意到在原串中,相鄰的回文子串,將變成,變換後的串中,以#為間隔的回文子串。

之後再掃一遍,看能否更新周圍的\(ll\)和\(rr\)。

注意,這個更新是單向的。比如說\(rr[i]\)表示的是以\(i\)為起點的最長回文子串長度,那麼就只能更新它右邊的\(rr[i+2]\)。\(ll[i]\)同理。

#include#include#include#include#include#include#include#include#include#define il inline

#define re register

#define ll long long

#define ull unsigned long long

#ifdef th

#define debug printf("now is %d\n",__line__);

#else

#define debug

#endif

using namespace std;

templateinline void read(t&x)

x*=fu;

}inline int read()

return x*fu;

}int g[55];

templateinline void write(t x)

while(x);

for(int i=g;i>=1;--i)putchar('0'+g[i]);putchar('\n');

}int n;

char ch[500010];

int r[500010],ll[500010],rr[500010];

int main()

ch[1]='#';

ch[2*n+2]='@';

for(int i=1,mx=1,p=1;i<=2*n+1;i++)

int ans=0;

for(int i=1;i<=2*n+1;i+=2)

write(ans);

return 0;

}

注意第88行(倒數第5行)必須先判斷ll和rr都有值,才能更新ans。

hack資料類似於awa,錯誤答案3,正確輸出2。因為程式在計算第乙個#(以及最後乙個#)時,\(ll=3,rr=0\)。這種只有一邊有回文串的端點不應被更新答案。

在luogu被hack,但是這裡沒有awa.

求乙個只含ab的字串有多少個非連續對稱子串行

位置和字元都關於某條對稱軸對稱。

選取的子串行不能是連續的一段(即不能是乙個子串)。

答案對\(10^9+7\)取模。

\(|s|\le 10^5\)。

首先明確乙個公式,非連續對稱子串行數=回文子串行數-回文子串數

回文子串數可以用manacher求出。

考慮怎麼求回文子串行數?

注意到用manacher求出的是以每條對稱軸所構成的最長回文子串的長度,其實是計算了每個對稱軸對答案的貢獻。

那麼回文子串行數也可以用類似的思想。

設\(f[(i+j)/2]\)是以\(i,j\)中間點為對稱軸,有多少對稱的字元。

這個可以用\(fft\)求。

類似於manacher,可以通過插#把\((i+j)/2\)變成\(i+j\)。

那麼這個對稱軸受到的貢獻就是\(2^-1\)。(每一對對稱的字元都可以有選和不選兩種情況,但是唯獨有一種——一對都不選——不可以,所以要減去1)。

總的來說就是兩遍\(fft\)+快速冪+manacher

manacher演算法 例題

簡單而有通俗的講解,講的太好了 證明對於一些我的理解,我會以 注釋的形式寫在 裡,我不懶 char str maxn char temp maxn 1 10 擴充套件後的字串 int len maxn 1 10 擴充套件後字串第i個位置回文串從中間到第有邊界的長度 相當於 回文子串長度 2 1 在用...

Trie 入門例題彙總

trie 字典樹 是一種實現字串快速檢索的多叉樹結構。trie的每個節點都擁有若干個字元指標,若在插入或檢索時掃瞄到乙個字元c,就沿著當前的節點的c字元指標,走向該指標指向的節點 trie結構一種典型的用空間換取時間的資料結構,其空間複雜度為o nc 其中n代表節點的個數,c代表著字符集的大小。一 ...

拓撲排序模板加例題(拓撲排序問題彙總)

概念 乙個有向無環圖的拓撲序列是將圖中的頂點排成乙個線性序列,使得對於圖中任意一對頂點u,v。若存在邊,則線性序列中u出現在v之前。演算法實現 1 若圖中的點入度均大於0則不存在拓撲序列,否則進行第二步 2 取乙個入度為0的點u並將其放置序列末尾 3 刪除點u以及從u伸出的邊,即將與u相連的點的入度...