優秀的拆分 NOI2016

2022-05-27 11:27:11 字數 2674 閱讀 5122

如果乙個字串可以被拆分為 \(\text\) 的形式,其中 \(\text\) 和 \(\text\) 是任意非空字串,則我們稱該字串的這種拆分是優秀的。

例如,對於字串 \(\texttt\) ,如果令 \(\text=\texttt\),\(\text=\texttt\),我們就找到了這個字串拆分成 \(\text\) 的一種方式。

乙個字串可能沒有優秀的拆分,也可能存在不止一種優秀的拆分。

比如我們令 \(\text=\texttt\),\(\text=\texttt\),也可以用 \(\text\) 表示出上述字串;但是,字串 \(\texttt\) 就沒有優秀的拆分。

現在給出乙個長度為 \(n\) 的字串 \(s\),我們需要求出,在它所有子串的所有拆分方式中,優秀拆分的總個數。這裡的子串是指字串中連續的一段。

以下事項需要注意:

出現在不同位置的相同子串,我們認為是不同的子串,它們的優秀拆分均會被記入答案。

在乙個拆分中,允許出現 \(\text=\text\)。例如 \(\texttt\) 存在拆分 \(\text=\text=\texttt\)。

字串本身也是它的乙個子串。

每個輸入檔案包含多組資料。

輸入檔案的第一行只有乙個整數 \(t\),表示資料的組數。

接下來 \(t\) 行,每行包含乙個僅由英文小寫字母構成的字串 \(s\),意義如題所述。

輸出 \(t\) 行,每行包含乙個整數,表示字串 \(s\) 所有子串的所有拆分中,總共有多少個是優秀的拆分。

\(n\le 30000\)

太良心了

\(85\%\)的點\(n\le 500\),直接\(o(n^3)\)暴力列舉區間+斷點用雜湊判斷

然後只要稍微動動腦子:設\(a[i]\)表示以\(i\)結尾的\(\text\)串個數,\(b[i]\)表示以\(i\)開頭的\(\text\)串個數,那麼答案就是\(\sum\limits_^

a[i]*b[i+1]\)

\(o(n^2)\) 95分到手

最後五分如果想不出來不拿也感覺無所謂。。。最後五分確實不好想

所以開始說正解:

上面的95分解法問題就在於\(a[n],\ b[n]\),我們需要\(o(n^2)\)的時間求出來,考慮怎麼樣求得更快

我們列舉乙個\(len\)表示我們現在想找到那些長度為\(2*len\)的\(\text\)串

然後在原串上每隔\(len\)放乙個斷點

我們列舉相鄰的兩個斷點\(i,j\),現在我們想要知道 以\(i\)開頭的字尾與以\(j\)開頭的字尾的最長公共字首(lcp) 和 以\(i\)結尾的字首與以\(j\)結尾的字首的最長公共字尾(lcs)

lcp可以用字尾陣列求;lcs也可以,把原陣列翻轉之後就變成字尾的lcp了,所以這兩個都是可以用st表\(o(1)\)求出的

那麼現在我們求出了這兩個值

情況1

對於這種情況,即\(lcp+lcs-1,我們是找不出\(\text\)串的

情況2

用腳畫圖 不愧是我

\(lcp+lcs-1,這個時候就有很多的長為\(2*len\)的\(\text\)串了,圖中畫出的\(\text,\ \text\)就是最靠左和最靠右的兩個這樣的串

實際上,我畫了"ok"的那個橙色區間的每乙個點都是乙個長為\(2*len\)的\(\text\)串的開頭,應該很好理解吧。。。

如何找哪一段是合法\(\text\)串的結尾也同理

所以實際上每次就是把\(a[n]\)和\(b[n]\)的某一段全部加一 用差分來維護一下就行了

最後來看一下時間複雜度

字尾陣列+st表是\(o(n\log n)\)

\(\frac+\frac+\frac+\dots+\frac\) 我記得差不多就是\(o(n \log n)\)吧。。。可能要稍微大一點

總之\(n\le 30000\)的資料是完全沒有壓力的

注意多組資料初始化陣列!注意多組資料初始化陣列!注意多組資料初始化陣列!

#include #define n 60005

using namespace std;

int t, n, nn;

char s[n];

int a[n], b[n];

int sa[n], sa2[n], rnk[n], sum[n], key[n], height[n], st[n][21];

inline bool check(int *num, int aa, int bb, int l)

void da()

} }void geth()

}void prest()

} }for (int i = 1; i <= nn; i++)

long long ans = 0;

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

printf("%lld\n", ans);

} int main()

nn = n;

n = (n<<1|1);

da(); geth(); prest();

solve();

} return 0;

}

NOI2016 優秀的拆分

看到題目,資料範圍有點怪異。對於95 的資料,對於100 的資料,意思是只有5分是正解。好吧,95pts的 很明顯,答案就是 而如何才能拿到100pts呢?我們可以先列舉a段的長度,很明顯每個長度為lcp,與往後求lcs,若 這樣就可以通過 include include include inclu...

NOI2016 優秀的拆分

題目實際上要求我們求從每個點出發的aa串的數量 考慮點i的答案,發現如果字首i與字首j j i 的最長公共字尾 i j,那麼i點出發向前就存在乙個長度為i j的aa串,題目即求對於每個字首,有多少個在他之前的字首滿足條件 考慮字尾自動機,由於每個字首都是字尾自動機parent樹上的一點,即兩個字首的...

NOI2016 優秀的拆分

點此看題 首先轉化問題,我們可以求出a i b i a i b i a i b i 即以i ii結束 開始的aaaa aa串的數量,這樣答案就可以表示為 a i b i 1 sum a i times b i 1 a i b i 1 求這兩個陣列,可以隔距離len lenle n設定乙個點,這樣乙個...