【復習筆記】2021-12 字符串


本文主要是記錄復習模板的情況,個人感覺做一些Luogu上的比賽題也可以綜合練習套路


后綴自動機

  • 線段樹維護 endpos 例題,注意這里線段樹合並的時候多是需要新建節點的

  • 計算子串在多少個模板串中出現,可以建立廣義 SAM 后對於每一個模板串找到其所有前綴在廣義 SAM 上的節點,然后從它們開始暴力跳 parent 樹算貢獻,並給每個訪問了的節點打上訪問標記保證在同一個模板串的計算過程中一個點不經過兩次的做法復雜度為 \(\Theta(n\sqrt n)\),證明考慮根號分治

  • 先區別叫法:按照正序加入自動機再連接 \((i,fail_i)\) 得到的樹是 \(\rm{Parent\ Tree}\),而逆序加入得到的是后綴樹

  • \(\rm{Parent\ Tree}\) 上跳 \(\rm{fail}\) 表示取后綴,走自動機上的出邊表示在當前字符串后面添加字符,在后綴樹上進行這些操作含義同但是方向完全取反

  • \(\rm{Parent\ Tree}\) 上根鏈操作表示將這些后綴進行一次在當前最后一次出現位置上進行更新:出現次數的增加/更新最后一次出現位置

  • 廣義 \(\rm{SAM}\) 的可能正確寫法是加入的時候判斷 \(\rm{las}\) 是否已經有這個兒子,也因此可以任意調整 \(\rm{las}\) 的值進行加入新點,可以應用於 \(trie\) 樹上建后綴自動機

  • \(\rm{LCT}\) 維護 \(\rm{SAM}\) 得到的 \(parent\) 樹或者后綴樹是一個常見技巧

    基於是不是強制在線要求是不是需要寫 access 之外的函數,Luogu7361 是統一處理,可以離線,而另外的 \(\rm{Luogu}5212\) 不行,需要寫全套

  • 使用 \(\rm{Parent\ Tree}\) 或者后綴樹維護字典序(例如求 \(\rm{SA}\)

    對於樹上同一節點的子節點的代表元是 \(\rm{str[len[fa[x]]+pos[x]]}\),連邊的時候排序即可

    直接求 SA 需要注意比較的是前綴信息,所以使用的是倒序建立的后綴樹

Luogu5115

可能的計算答案方式並不多,嘗試按照位置統計貢獻失敗之后轉行來按照每個字符串來統計貢獻

觀察一下一個特定長度的字符串會帶來多少的貢獻:

\[\sum_{i=1}^{len}(len-i+1)i[i\le k_1][len-i+1\le k2] \]

\(len\leftarrow len+1\) 時,增量只有三種:添加 \(i=len+1\) 的貢獻/刪掉 \(i=len+1-k_1\) 的貢獻/\((len-i+1)i\) 的變化量,可以 \(\Theta(n)\) 遞推得到

剩下的問題就是每個字符串在統計進答案的時候一定要保證是極長的,由於 \(endpos\) 的原因,在節點對應的字符串前面加入相同字符不再可行,但是可以在后面加

注意到 \(SAM\) 每個節點可以維護出來對應在原字符串中的后面一個字符,於是每次統計的時候用總的對數減去后繼相同的對數即可

時間復雜度 \(\Theta(n\Sigma)\)

Luogu7361

能離線就離線,所以掃描線,觀察添加最后一個節點會帶來哪些字符串出現了第二次

首先在 \(\rm{LCT}\) 上面維護第一次/第二次出現的位置,此時每個 splay 上的點最后一次出現次數相同

不難發現在 access 的過程中,跳虛邊的父節點是能經過的鏈上的字符串長最大值,又由於最后一次出現節點被更新成一樣的了,又因為是在同一個 splay 上,更新前視角下最后一次出現也是一樣的,所以它可以代表整個 splay 來更新

此時考察答案的形式:一個節點代表的字符串被完全包含在區間里面/被部分包含

如果完全包含直接用 \(len[x]\) 更新答案,否則用 \(\rm{last-left}\) 來更新

由於是掃描線,所以維護兩個線段樹維護兩種情況下最大值,每次跳虛邊時給能更新的更新即可

注意不能包含時對答案的貢獻一定是 \(\rm{rpos-queryl}\) 而每個葉子對應的 \(\rm{queryl}\) 一定,所以維護 \(\rm{rpos}\) 最值

后綴數組

  • 后綴排序巧妙之處其一是在倍增過程中利用已經比較出來順序的字符串,其二就是雙關鍵字基數排序:

    計算第二關鍵字排序為 \(i\) 的數第一關鍵字的位次,在基數排序的過程中倒序在桶里面減少,這樣子達到了基數排序的目的

  • 求解 \(\rm{LCP}\) 的過程中注意是在 (rk[i]+1,rk[j]) 之中找最小值

    這里的 \(height\) 定義在 \(\rm{sa}\) 數組上那么還有一個啟示是:一個區間中數的 \(\rm{LCP}\) 是兩兩 \(\rm{LCP}\) 的最小值

  • 樹上后綴排序和序列上的后綴排序本質相同,選取的第二關鍵字不過變成了 \(2^k\) 級祖先( Luogu5346 涉及到了三關鍵字排序,先排后兩個,將后兩個的排序結果作為第二個來使得其不可重復就行了)

  • 后綴平衡樹:動態維護 \(\rm{SA}\) 數組,只能在字符串首加入字符(如果全是在串尾操作並比較字典序可以直接維護反串,但翻轉字符串之后就不能正序比較字典序)

    典例是維護求 \(SA\),逆序加入字符,使用替罪羊樹維護后綴之間的字典序,由於平衡樹 Insert 操作本質上是一個平衡樹二分的過程,考察比較節點上代表的后綴字符串和當前字符串的順序的過程:

    特判第一個字符不同的情況,否則大小關系依賴於已經比較過的兩個后綴的大小關系,可以訪問數組 key 得到,其中每個節點的 \(key\) 等於其前驅后繼 \(key\) 的平均值,由於 double 精度的問題推薦使用 \(2^{63}\) 作為哨兵節點的 \(key\)

    刪除串首的后綴也是可行的,可以直接使用平衡樹操作解決,也不用更新被影響 key 因為只關注其大小關系,應該不能隨便刪除字符

Code Display
inline void push_up(int x){siz[x]=siz[ls[x]]+siz[rs[x]]+(bool)x; return ;}
/*-------------------------------*/
int bin[N],nds;
inline bool Unb(int node){return siz[node]*alp<siz[ls[node]]||siz[node]*alp<siz[rs[node]];}
pair<int,int> up;
pair<double,double>v;
int unb=-1;
inline int build(int l,int r,double lv,double rv){
	if(r<l) return 0;
	int mid=(l+r)>>1,now=bin[mid]; double mv=(lv+rv)/2;
	key[now]=mv;
	ls[now]=build(l,mid-1,lv,mv); rs[now]=build(mid+1,r,mv,rv);
	return push_up(now),now;
}
/*-------------------------------*/
inline void dfs(int p){if(ls[p]) dfs(ls[p]); bin[++nds]=p; if(rs[p]) dfs(rs[p]); return ;}
inline bool les(int x,int y){
	if(s[x]!=s[y]) return s[x]<s[y];
	if(y==1) return 0;
	return key[x-1]<key[y-1];
}
inline void insert(int &rt,int pos,double l,double r){
	if(!rt){
		rt=pos; key[rt]=(l+r)/2; siz[rt]=1;
		ls[rt]=rs[rt]=0;
		return ;
	}
	if(les(pos,rt)){
		insert(ls[rt],pos,l,key[rt]);
		if(Unb(ls[rt])) up={rt,0},unb=ls[rt],v={l,key[rt]};
	}else{
		insert(rs[rt],pos,key[rt],r);
		if(Unb(rs[rt])) up={rt,1},unb=rs[rt],v={key[rt],r};
	} return push_up(rt);
}
inline void Insert(int pos){
	unb=-1; insert(rt,pos,0,lim);
	if(Unb(rt)){
		nds=0; dfs(rt);
		rt=build(1,nds,0,lim);
	}else if(~unb){
		nds=0; dfs(unb);
		(up.sec?rs:ls)[up.fir]=build(1,nds,v.fir,v.sec);
	}
}
inline void del(int &rt,int pos){
	if(pos==rt){
		if(!ls[rt]||!rs[rt]) rt=rs[rt]+ls[rt];
		else{
			int fat=rt,p=ls[rt];
			if(!rs[p]) rs[p]=rs[rt],rt=p;
			else{
				while(rs[p]) siz[fat=p]--,p=rs[p];
				rs[fat]=ls[p]; ls[p]=ls[rt]; rs[p]=rs[rt]; rt=p;
			}
		} return push_up(rt);
	}
	if(les(pos,rt)) del(ls[rt],pos);
	else del(rs[rt],pos);
	return push_up(rt);
}

LOJ6498

不枚舉所有 \((i,j)\) 是不可行的,那么考慮怎么枚舉

欽定 \(xor\) 是不可行的,所以欽定 \(\rm{LCP}\),配合可持久化 \(0/1 \ trie\)

\(\rm{height}\) 數組上維護出來每個元素作為最小值的區間,每次掃左邊的區間,在右邊的區間對應的 \(0/1\ trie\) 上二分即可

優秀的拆分

非常巧妙的做法,目前做的題里面用到的非常多

\(C_1[i]\) 表示 有幾個 \(AA\) 結尾在 \(i\) 點,\(C_2[i]\) 表示有幾個 \(BB\) 開始於 \(i\) 點,答案就是 \(\sum_i C_1[i]\times C_2[i-1]\)

那么考慮如何計算出來這兩個數組,其本質就是維護有多少個相同且相連的子串,做法是每 \(len\in [1,n]\) 個設置一個關鍵點,使用 \(\rm SA\) 求出關鍵點之間的 \(\rm LCP,LCS\) 就做完了

Z-Function

Z 函數算法本質是減少冗余,盡可能利用已經計算過的信息

if(i<=r) z[i]=min(r-i+1,z[i-l+1]); 一句中就很好體現了這點 ,這也恰好保證了整個處理過程復雜度

這和經典的難背 \(\texttt{Manacher}\) 算法思路類似,后者代碼中是

if(i<=r) r[i]=min(r[(i<<1)-r],rmax-i); else r[i]=1;

(所以 \(\texttt{Manacher}\) 就不單獨寫了 )

CF432D

主要是處理前綴在字符串中出現次數:

z[i] 計算的是每個后綴和全串的 \(\rm{LCP}\),那么對於一個前綴 \(S[1\dots l]\),滿足 \(z_j\ge l\)\(j\) 為起始點,必然出現了一次

不難發現子串在母串出現必然有唯一起始點,所以該做法可以不重不漏統計

直接對 \(z_i\) 開桶然后后綴和即可

LOJ6158

翻轉 \(S\) 串變成整串匹配后綴的形式,寫暴力觀察發現可以加速的部分是讓整串和后綴和為 \(9\)

使用 Z 函數求一個 \(T[i]=9-S[i]\)\(S[i]\) 的相等情況進行匹配即可

細節稍多,需要注意匹配完和為 9 的部分之后在一個串上走一段 9 和 翻轉串之后存在的前導 \(0\)

AC 自動機

  • \(\rm{ACAM}\) 上求子串信息的方式是在自動機上走出邊,每走一步掃一下 \(Fail\) 樹上的根鏈

  • 比較關鍵的是如果一個節點類似被標記非法,其 \(fail\) 樹上根鏈上所有點也要被標記,可以直接暴力跑,因為每個點最多被標記一次

Luogu7582

其實是明示根號做法的,根號個詢問一修改的做法好像重新統計貢獻處出了點問題,所以簡記對根號個字符串開 \(\rm{ACAM}\) 的做法

如上,對根號個字符串開一個 \(\rm{ACAM}\),每次查詢直接在這些 \(\rm{ACAM}\) 上跑,跳到一個節點就掃根鏈,求出來這些字符串出現了多少次,

考慮修改權值的操作:維護一個 \(tag\),表示被推平成了 \(0\) 再使用區間加法把 \(k\) 扔到另一個維護的變量 \(\rm{delta}\) 上,

上述情況中邊角重構復雜度可以接受,直接重構即可,完全覆蓋的塊里面見機行事得到答案

這里躲不開根號平衡,也就是說有 \(q\) 次修改但是有 \(q\sqrt n\) 次查詢,那么選擇一個根號修改,\(\Theta(1)\) 查詢的數據結構就行了

回文自動機

  • 一種不基於暴力跳 \(fail\) 的插入方式:考察跳 \(fail\) 的目的:找到一個節點使得起前驅和當前所需一致

    不難發現需求是量級是 \(\Theta(\Sigma)\) 的,嘗試開數組 \(qu[x][i]\) 記錄,如果字符集較大就使用可持久化數組

    考察從 \(fail\) 到當前節點變化只有 \(fail\) 它自己,所以直接修改即可

    實現的時候注意使用 \(qu\) 數組的時候特判一步走到的情況,因為這並不會在數組里面得到體現,同時注意奇根和偶根的的不同點:空兒子指向偶根,沒 \(fail\) 找奇根

    這種加字符的方法在把 \(\rm{trie}\) 轉化成 \(\rm{PAM}\) 時有降低復雜度的功效

  • 從頭插入:

    本質上是維護最長回文前綴,不難發現這個量本質上就是最長回文后綴,所以可以維護兩個插入指針:\(\rm{front,tail}\)(有別於原來單一的 \(last\) ),往哪邊加入字符就更新誰

    這里需要注意如果插入得到的節點長度是全串長就要把兩個指針都放到這個新建的節點上面

  • 從頭/尾刪除一個字符:(這部分全是口胡,因為沒找到例題)

    一個節點是重要的當且僅當它是對於其兩個端點都是極長的回文串,在回文樹上維護每個節點 \(imp\) 值和回文樹上的兒子數,其中 \(imp\) 值表示在整串的出現中有哪些是重要的

    后端插入會讓最長回文前綴不重要,前端插入可能會讓最長回文后綴不重要,用 map 維護 \(cnt[l][r]\) 表示被標記了多少次不重要,值是 \(0\) 說明重要

    刪除的判定是 \(imp\) 和兒子數量都是 \(0\),因為兒子空了所以不會被標記不重要,如果 \(imp\) 還是 \(0\) 就說明真的沒有出現過了,刪掉的時候需要修改最長回文前后綴的 \(cnt\)\(fail\) 的兒子數

LOJ6070

簡記論文里面的 \(\Theta((n+q)\sqrt n)\) 的做法

將原串分成 \(\sqrt n\) 個部分,維護每個塊左端點到 \(|S|\) 的回文樹,由於回文樹每次最多添加一個節點,那么每個節點維護時間戳表示最左邊訪問到其的點即可

如果直接使用 \(n\Sigma\) 的前端插入復雜度會多 \(\Sigma\),但是上面是打標記那就一打到底,這部分也使用維護標記的做法來解決

注意到對於一個回文樹上的節點,其向前插入使用的 \(qu[x][i]\) 和向后插入使用的是一樣的,原理是跳的 \(fail\) 是一樣的,那么向后插入找的目標是被當前節點所包含的,直接對稱過去也就順理成章的一樣了

所以花費 \(n\Sigma\) 的復雜度直接建出完整串的回文自動機並求出所有 \(qu\) 指針,在進行處理每個塊左端點 \(x\) 到所有 \(\ge x\)\(y\) 時維護前端插入指針位置和后面這段已經在回文樹上覆蓋的節點數

對於每個詢問,\(l,r\) 在一個塊里面的直接暴力跑回文樹,否則繼承上面維護的前端插入指針信息,插入字符看看是不是新的就行了

2021-03-30 有趣的字符串題

使用 \(BIT\) 和掃描線維護答案,\(PAM\) 維護字符串

有一個結論:每個回文串在自動機上的祖先鏈,不構成等差的斷點不超過 \(\log n\)

自動機上的上下必然是 \(border\),那么根據回文和 \(border\) 的性質那么不等差必然差 \(\frac{len}2\) 以上

維護每個子串的最后一次的出現位置,在 \(PAM\) 上維護每個 \(end\) 對應的節點,使用線段樹維護回文樹(\(dfn\) 序)

對於掃描線時擴展的 \(r\) 在回文樹上跳非等差的 \(border\)

Lyndon Word

定義 \(\rm{Lyndon}\) 串為所有后綴(不要求是真后綴)中字典序最小的是本身的串,等價定義是所有循環同構中最小的是本身的串,等價性的證明可以簡單反證得到

Lyndon Word \(uv\) 滿足 \((u<v)\)\(u,v\) 都是 Lyndon Word,性質證明目的就是證 \(uv\) 是最小后綴,對於 \(u\)\(v\) 前綴的部分需要簡單反證

定義串 \(S\) 的 Lyndon 分解為將 \(S\) 分成 \(s_1\dots s_k\)\(\forall \ i\in [1,k-1] s_{i}\ge s_{i+1}\)

一個串有且僅有一個 Lyndon 分解,唯一性是平凡的,考慮存在性:

將原串 \(S\) 的每個字符作為初始 Lyndon Word,合並 \(s_i< s_{i+1}\) 直至不能再合並,最后得到的即是唯一的 Lyndon 分解

這個方式求太慢了,有一個 Duval 算法,維護 \(i,j,k\) 變量,\(i\) 表示前 \(i-1\) 完成了划分 \(k\) 表示 \(S[i\dots (k-1)]\) 划分成了一個循環串,循環節為 \(S[j\dots(k-1)]\)

添加 \(S_k\) 時分開討論:

  • \(S_k=S_j\) 直接指針加一

  • \(S_k<S_j\) 說明前面的串要完成划分,即 \(S_{i\dots k}\) 是划分中的一個新的確定段,因為如果按照循環節划分則小節小於大節

  • \(S_k>S_j\) 修改循環節(即 \(j=i\) )再繼續走即可,不能在這里划分的原因是最后划分得到的字典序單調不增

int i=1,j,k;
while(i<=n){
    k=(j=i)+1; 
    //notice pointer k is always the position waiting to be expanded
    while(k<=n&&s[j]<=s[k]){
        if(s[j]==s[k]) ++j; // maintain period
        else j=i; // rebuild period
        ++k;
    }
    while(i<=j) ans^=i+k-j-1,i+=k-j; 
    // k-j is length of the period without adding 1
}

求出來 Lyndon Word 可以求出來每個前綴的最大/最小后綴:最小后綴就是將這個前綴視作一個字符串進行 Lyndon 分解所得到的最后一段的起始位置

但是值得指出的是最大后綴並不是第一段的末尾,反例是下串:bcdabcde,求解還是需要翻轉字符集


字符串理論

弱周期引理

如果 \(p\)\(q\) 均為 \(s\) 的周期,且 \(p+q\le |s|\),那么 \(gcd(p,q)\) 也是 \(s\) 的周期

證明考慮輾轉相減,設 \(p<q\),則有 \(s[1]=s[p+1]=s[q+1]\),所以 \(q-p\) 也是 \(s\) 的周期

迭代即可證明

這里的 \(p+q\le |S|\) 的限制,考慮實際含義,也就是每個點向后面下標差為 \(i,j\) 的點連邊,那么聯通塊內的字符一樣

如果 \(p+q>|S|\),那么不難證明

周期引理

如果 \(p\)\(q\) 均為 \(s\) 的周期,且 \(p+q-gcd(p,q)\le |s|\),那么 \(gcd(p,q)\) 也是 \(s\) 的周期

關於為什么要加上 \(p+q-gcd(p,q)\le |S|\),以 \(\mathrm{S=ABACABA,p=4,q=6}\) 為例

此時 \(\mathrm{p+q-gcd(p,q)=8>|S|,gcd(p,q)}\) 也不是周期

證明不會,但是找到了資料:https://zhuanlan.zhihu.com/p/89385360

因為一些原因,hzoi機房看不了這個網站了機房不給開知乎這樣好的證明網站是怎么回事呢?yspm也很好奇

CF1205E

平方仍然化成點對的數量,也就是考慮有多少點對 \((i,j)\) 滿足 \(i\)\(\mathrm{period}\) 的同時 \(j\) 也是 \(period\)

考慮 \(\mathrm{period}\) 同時為 \(i,j\) 的組合意義,也就是每個點向后面下標差為 \(i,j\) 的點連邊所形成的聯通塊個數

\(i\)\(j\) 互質的時候

  • \(i+j\le n\),那么\(x\dots x+(i-1)j\)\(i\) 取模的結果均不同,那么對於下標差為 \(i\) 形成的鏈,必然可以連成一個聯通塊

  • \(i+j>n\),此時對於模 \(i\)\(0\) 的鏈不會有出邊,那么這個圖沒有環,所以此時聯通塊數為點減邊,即 \(n-(n-i+n-j)=i+j-n\)

對於不互質的情況

  • 若滿足 \(\frac{i+j}{gcd(i,j)}>\) \([1,n]\) 中模 \(gcd(i,j)\)\(x\) 的數,也就是 \(i+j>n\),此時仍沒有環

  • 否則套用弱周期引理可以證明有 \(gcd(i,j)\) 個聯通塊

至此我們把原問題轉化成了求下式:

\[\sum_{i=1}^{n-1}\sum_{j=1}^{n-1} k^{\max(gcd(i,j),i+j-n)} \]

將這個式子分成 \(gcd(i,j)>i+j-n\) 和假令 \(i+j-n\) 最大兩個部分

第二個部分也就是

\[\sum_{i=1}^{n-1}\sum_{j=1}^{n-1}k^{i+j-n} \]

枚舉 \(i+j>n\),可行的方案是 \(2n-(i+j)-1\),注意這里我們並不需要次數小於 \(0\) 的部分

第一個部分很巧:

\[\sum_{i=1}^{n-1}\sum_{j=1}^{n-1}[gcd(i,j)>i+j-n]k^{gcd(i,j)}-k^{i+j-n} \]

\[\sum_{g=1}^{n-1}\sum_{i=1}^{(n-1)/g}\sum_{j=1}^{(n-1)/g}[gcd(i,j)=1][i+j\le \lceil \frac{n}{g}\rceil] (k^g-k^{(i+j)g-n}) \]

這樣的限制並不好處理,按照上面的思路,枚舉 \(i+j\) 的值

\[\sum_{g=1}^{n-1} \sum_{s=2}^{\lceil \frac{n}{g}\rceil} \sum_{i=1}^{s-1} [gcd(i,s-i)=1] (k^g-k^{sg-n}) \]

這次的 \(gcd(i,s-i)=1\) 的求和顯然是 \(\varphi(s)\)

所以對於 \(k^g\) 的部分,對歐拉函數求前綴和可以做到 \(\Theta(n)\)

后面的部分,考慮到我們只關注 \(sg>n\) 的部分,那么不難發現每個位置至多有 \(1\) 項,也是 \(\Theta(n)\)

所以總復雜度就是 \(\Theta(n)\) 的,比那個用 \(\epsilon=\mu* I\) 的不知道高明到哪里去了

Border 理論

如下結論大多與等差數列有關,在題目中可能需要配合數據結構加以使用

  • \(2|S|\ge |T|\),那么 \(S\)\(T\) 中匹配的位置構成一個等差數列

證明設第一段和第二段的間距為 \(p\),第二,三段的間距為 \(q\),那么因為長度限制,\(p+q\le |S|\),首先得到 \(p,q\) 均為 \(S\)\(\rm{period}\)

使用弱周期引理得到 \(gcd(p,q)\)\(S\)\(\rm{preiod}\)

\(S\) 的最小正周期 \(r\le gcd(p,q)\) 所以得到 \(r\le gcd(p,q)\le q\le |S_1\cap S_2|\)

所以最小正周期是 \(|S_1\cup S_2|\) 的周期,也就是說如果 \(r<q\) 那么將首個匹配位置右移 \(r\) 個也可以匹配

按照上述定義,等差數列至少有 \(3\) 項,那么公差不難證明是最小周期,此時 \(r\le |S|/2\)

這個結論是直接從上面推的

  • 字符串不小於 \(len/2\)\(border\) 構成一個等差數列

考慮兩個周期 \(p,q\) 滿足 \(p<q<|S|/2\)\(n-p\) 是最大 \(border\)

再次使用弱周期引理 \(gcd(p,q)\) 也是周期,同時 \(n-q,n-p,n-gcd(p,q)\) 均為 \(border\)

注意到 \(gcd(p,q)\le p\),又有 \(n-p\) 是最大 \(\rm{border}\),所以得到 \(p=gcd(p,q)\),也就是 \(p|q\)

那么 \(border\) 就會有 \(n-p,n-2p,n-3p\dots\)

  • 推論:每個串的所有 \(\rm{border}\) 可以分成不超過 \(\log\) 組,每組長度為等差數列

把所有的 \(\rm{border}\) 的長度按照 \([2^{i-1},2^i)\) 這樣子分組,最后一段已經證明了

剩下的部分,對於一組里面的短的是長的的 \(border\),畫圖可以證,所以原命題也就得證了

WC2016 論戰捆竹竿

考慮暴力:設 \(period\) 集合為 \(\{S\}\) 最小數字為 \(x\),建立 \(x\) 個點表示 \(\bmod\ x\) 意義下的數字,每個點 \(i\)\((i+y)\% x\ (y\in S)\) 連邊,邊權為 \(y\)

之后以 \(0\) 為源點,求出來到每個點的最短路,表示在 \(\mod x\) 意義下最小能被表示出來的數字,大於其的同余數字一定能被表示出來(這種方法貌似被稱作 “同余最短路”)

\(5\times 10^5\)\(\log\) 死貼只有 \(\rm{border}\) 形成的等差數列數,考察一個等差數列能進行的松弛操作,設當前的等差數列為 \(\rm{(v_0,delt,len)}\) 不難發現連邊會形成 \(\rm{Gcd(v_0,delt)}\) 個環

對於每個環而言,在當前進行松弛前有一個最小值,它在這輪松弛不會改變最短路的值,那么以它為起始點拆成一條鏈,考察松弛的方式就是每個點找到鏈上在其前且距離其不超過 \(len\) 的點進行 \(\rm{dp_t+v_0+k\times delt}\) 的轉移

那么使用單調隊列進行優化單個等差數列里面的部分,復雜度是 \(\Theta(n\log n)\),剩下的是考慮合並若干個等差數列的松弛結果:

不同的等差樹立本質上是 \(v_0\) 的變化,每個原來的點直接挪給新點,另外新增的轉移是加上若干個 \(v_{pre}\),其實還是等差數列,但是項數沒有限制,找到環上最小值記錄前綴 \(\min\) 即可


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM