本篇隨筆面向個人
本來以為回文串很簡單,但是沒有做對應的練習前下此定論為時過早。
雖然例題中也沒有模板題(因為太簡短了……),但是有必要預先打一遍。
將原字串頭部插入$
,尾部插入@
或\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.
求乙個只含a
和b
的字串有多少個非連續對稱子串行。
位置和字元都關於某條對稱軸對稱。
選取的子串行不能是連續的一段(即不能是乙個子串)。
答案對\(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相連的點的入度...