2019HNCPC Distinct Substrings (擴充套件KMP)
2019HNCPC Distinct Substrings
Mean
給定一個\(n\)個整數的字串,定義\(f(s_1,s_2,...,s_n)\)為本質不同的子串個數,
對於每個整數\(c∈[1,m]\),分別輸出\(f(s_1,s_2,...,s_n)-f(s_1,s_2,...,s_n,c)\).
\(n,m<=1e6\)
Sol
擴充套件\(kmp\).
似乎是個經典做法?
參考OIWKI
給定一個長度為 \(n\)的字串\(s\),計算\(s\)的本質不同子串的數目。
考慮計算增量,即在知道當前\(s\)的本質不同子串數的情況下,計算出在\(s\)末尾新增一個字元後的本質不同子串數。
令\(k\)為當前\(s\)的本質不同子串數。我們新增一個新的字元\(c\)至\(s\)的末尾。顯然,會出現一些以\(c\)結尾的新的子串(以 \(c\)結尾且之前未出現過的子串)。
設串 \(t\)是\(s+c\)的反串(反串指將原字串的字元倒序排列形成的字串)。我們的任務是計算有多少\(t\)的字首未在\(t\) 的其他地方出現。考慮計算 \(t\) 的 \(Z\) 函式並找到其最大值 \(zmax\)。則 \(t\) 的長度小於等於 \(zmax\) 的字首的反串在 \(s\) 中是已經出現過的以\(c\)結尾的子串。
所以,將字元\(c\) 新增至 \(s\) 後新出現的子串數目為 \(|t|-zmax\)
演算法時間複雜度為\(O(n^2)\)。
值得注意的是,我們可以用同樣的方法在 \(O(n)\)時間內,重新計算在端點處新增一個字元或者刪除一個字元(從尾或者頭)後的本質不同子串數目。
按照上述做法,先將\((s_1,s_2,...,s_n)\)翻轉成\((s_n,s_{n-1},...,s_1)\),然後求一下\(Z\)函式。
最後考慮若加入\(c\),在對於所有\((s_n,s_{n-1},...,s_1)\)中,\(s_i=c\),則需要將\(z_{i+1}\)代入\(c\)的情況取\(Max\),開一個桶維護一下即可。
剩下的就是如上的貢獻計算了,具體看程式碼。
Code
#include<bits/stdc++.h> using namespace std; const int N = 1e6+10; int mx[N]; typedef long long ll; const ll mod = 1e9+7; // C++ Version int a[N]; int s[N]; void z_function(int s[],int n) { for (int i = 1, l = 0, r = 0; i < n; ++i) { if (i <= r && a[i - l] < r - i + 1) { a[i] = a[i - l]; } else { a[i] = max(0, r - i + 1); while (i + a[i] < n && s[a[i]] == s[i + a[i]]) ++a[i]; } if (i + a[i] - 1 > r) l = i, r = i + a[i] - 1; } } int n,m; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ int maxx=0; for(int i=0;i<n;++i){ scanf("%d",&s[i]); maxx=max(maxx,s[i]); a[i]=0; } for(int i=0;i<=m;++i){ mx[i]=0; } reverse(s,s+n); z_function(s,n); for(int i=0;i<n-1;++i){ mx[s[i]]=max(mx[s[i]],1+a[i+1]); } mx[s[n-1]]=max(mx[s[n-1]],1); ll ans=0; ll jc=1; for(int i=1;i<=m;++i){ jc = jc*3%mod; if(mx[i]==0){ ans^= ((n+1)*jc%mod); } else{ ans^= (((n+1)-mx[i])*jc%mod); } } printf("%lld\n",ans); } return 0; }