2019ICPC(徐州) - Colorful String(哈希+二分+动态规划/回文自动机)
題目鏈接:點擊查看
題目大意:給出一個字符串,詢問該字符串中的所有回文子串中,各有多少不同的字母
題目分析:這個題題意很簡單,在比賽的時候看到字符串第一反應是哈希,哈希+暴力+線段樹果不其然的T掉了。。還超了一秒鐘,完完全全的是算法的問題,所以就沒再碰那個題了,賽后對這個題一直很在意,磨了一個星期,可算是磨出來了,期間的過程也是艱難險阻嗷,這個題目的正解是用回文自動機來做(好像一切有關于回文串的問題都能用這個來做,太強了),于是就想去偷學一波,因為需要前置知識是關于fail指針的理解,就先花了兩天肝會了KMP,然后又去看回文自動機了,看了兩天還是不太懂fail邊是怎么建的,只會用模板,但不太會用,隨后去網(wǎng)上看有沒有別的方法,還有一個馬拉車的,還有一個就是哈希+二分判斷回文串的,學了一手,就可以用logn的時間來判斷每個點為中點的最長回文串了,再配合上動態(tài)規(guī)劃記錄一下每個字母上一次出現(xiàn)的位置,就可以以O(26)的時間復雜度查詢一段區(qū)間內每個字母出現(xiàn)過的次數(shù)了,這一塊我也不太懂,是交給隊友研究的,最后一組合起來,二分+哈希+動態(tài)規(guī)劃,總時間復雜度是n*logn*26,完美解決了這個問題
等以后學會了回文自動機在來回顧一下這個題吧。。現(xiàn)在還是太菜了
2020.2.6更新:
刷字符串剛好想起來還有這樣一道題,回來看了一眼題意,回文自動機模板粘貼,稍微修改一下內部實現(xiàn),10分鐘不到一氣呵成完成AC,回首看看半年前的自己是真的弱,當時徐州這場比賽我們隊出了三個題目,在學校里的rank是倒數(shù)第一。。不多說了,今年加油吧
回文自動機來解這個題的話,只需要在每個節(jié)點再維護一下顏色的個數(shù)就好了,我選擇的是用狀態(tài)壓縮,因為一共才26個字母,一個int整數(shù)就可以保存,題目問的是每個回文子串的貢獻,所以我們還需要維護一下每個節(jié)點所出現(xiàn)的次數(shù),最后O(n)遍歷一遍每個節(jié)點維護答案就好了
上代碼:
哈希+二分+動態(tài)規(guī)劃:
#include<iostream> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=3e5+100;ull hash1[N],hash2[N],p[N];string str;int n;void HASH() {hash1[0]=0;hash2[n+1]=0;for(int i=1;i<=n;i++)hash1[i]=hash1[i-1]*131+str[i]-'a'+1;for(int i=n;i>=1;i--)hash2[i]=hash2[i+1]*131+str[i]-'a'+1;p[0]=1;for(int i=1;i<=n;i++)p[i]=p[i-1]*131; } int pos[N][30];bool check(int i,int len,bool sta)sta=1:奇數(shù)回文串 sta=0:偶數(shù)回文串 {int l,r;if(sta){l=i-len;r=i+len;}else{l=i-len;r=i+len+1;}if(l<=0||r>n)return false;ull a=hash1[r]-hash1[l-1]*p[r-l+1];ull b=hash2[l]-hash2[r+1]*p[r-l+1];return a==b; }LL getval(int l,int r)//獲得區(qū)間內有多少不同的字母 {LL ans=0;for(int i=0;i<26;i++){LL temp=pos[r][i]-l+1;if(temp<0)continue;ans+=temp;}return ans; }int main() { // freopen("input.txt","r",stdin);while(cin>>str){n=str.size();str=" "+str;HASH(); for(int i=0;i<26;i++)pos[0][i]=-1;for(int i=1;i<=n;i++)//dp存每個字母前一次出現(xiàn)的位置 {for(int j=0;j<26;j++)pos[i][j]=pos[i-1][j];pos[i][str[i]-'a']=i;}LL ans=0;for(int i=1;i<=n;i++)//統(tǒng)計所有奇數(shù)回文串{int l=0;int r=n;int len;while(l<=r){int mid=l+r>>1;if(check(i,mid,1)){len=mid;l=mid+1;}else{r=mid-1;}}int x=i-len;int y=i;ans+=getval(x,y);}for(int i=1;i<n;i++)//統(tǒng)計所有偶數(shù)回文串{int l=0;int r=n;int len=-1;while(l<=r){int mid=l+r>>1;if(check(i,mid,0)){len=mid;l=mid+1;}else{r=mid-1;}}if(len==-1)continue;int x=i-len;int y=i;ans+=getval(x,y);}printf("%lld\n",ans);}return 0; }回文自動機:
#include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cmath> #include<cstring> #include<algorithm> #include<stack> #include<climits> #include<queue> #include<map> #include<set> #include<sstream> using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=3e5+100;char s[N];int n;struct Palindrome_tree {int nxt[N][26];int fail[N]; // 當前節(jié)點最長回文后綴的節(jié)點int len[N]; // 當前節(jié)點表示的回文串的長度int cnt[N]; // 當前節(jié)點回文串的個數(shù), 在getcnt后可得到全部int sed[N]; // 以當前節(jié)點為后綴的回文串的個數(shù)(并不是表示第i結尾的回文串的種類數(shù),如果要求每個點結尾的數(shù)的回文串個數(shù),得用last)int record[N]; //record記錄了節(jié)點回文串的結束位置int color[N];//顏色 char s[N];int tot; // 節(jié)點個數(shù)int last; // 上一個節(jié)點int n;//當前字符串的長度 void init(){tot = n = 0;memset(fail, 0, sizeof fail);memset(cnt, 0, sizeof cnt);memset(sed, 0, sizeof sed);memset(len, 0, sizeof len);memset(nxt, 0, sizeof nxt);}void build(){len[0] = 0, len[1] = -1; // 0為偶數(shù)長度根, 1為奇數(shù)長度根tot = 1, last = 0;fail[0] = 1;}int getfail(int x, int n){while (s[n - len[x] - 1] != s[n]||n-len[x]-1<0) // 比較x節(jié)點回文串新建兩端是否相等//n-len[x]-1<0這個是我自己加的,多組的時候光第一個條件是不夠的,所以有錯請手動刪除x = fail[x]; // 若不同, 再比較x后綴回文串兩端return x;}void insert(char ch){int c = ch - 'a';//全小寫要用a 全大寫要用A 不然會錯s[++n]=ch;int p = getfail(last, n);// 得到第i個字符可以加到哪個節(jié)點的兩端形成回文串if (!nxt[p][c]){tot++;len[tot] = len[p] + 2; // 在p節(jié)點兩端添加兩個字符color[tot]=color[p]|(1<<c);fail[tot] = nxt[getfail(fail[p], n)][c]; //tot點的后綴回文,可以由上一個節(jié)點的后綴回文嘗試得到sed[tot] = sed[fail[tot]] + 1; // 以當前節(jié)點為結尾的回文串個數(shù)nxt[p][c] = tot; // 新建節(jié)點}last = nxt[p][c]; // 當前節(jié)點成為上一個節(jié)點cnt[last]++; //當前節(jié)點回文串++record[last] = n;}void get_cnt(){for (int i = tot; i > 0; i--)cnt[fail[i]] += cnt[i];//fail[i] 的節(jié)點 為 i 節(jié)點的后綴回文串, 所以個數(shù)相加} }tree;int get_num(int num) {int cnt=0;while(num){if(num&1)cnt++;num>>=1;}return cnt; }int main() { //#ifndef ONLINE_JUDGE // freopen("input.txt","r",stdin); //#endif // ios::sync_with_stdio(false);scanf("%s",s);n=strlen(s);tree.init();tree.build();for(int i=0;i<n;i++)tree.insert(s[i]);tree.get_cnt();LL ans=0;for(int i=1;i<=tree.tot;i++)ans+=tree.cnt[i]*get_num(tree.color[i]);printf("%lld\n",ans);return 0; }?
總結
以上是生活随笔為你收集整理的2019ICPC(徐州) - Colorful String(哈希+二分+动态规划/回文自动机)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (转)KMP算法原理讲解及模板C实现
- 下一篇: 2019ICPC(沈阳) - Fish