PAM 入门
前后端插入
题目链接:https://vjudge.net/problem/HDU-5421
注意到回文后缀也是回文前缀,所以大体上没有变化。
只是要维护最长回文前缀和后缀,当最长回文前/后缀等于原串时,记得更新另外一个。
时间复杂度:$O(n)$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
   | #include<cstdio> #include<cstring> #include<iostream> #include<string> using namespace std; typedef long long LL; const int N = 1e5 + 5; int len[N], fail[N], go[N][26], llast, rlast, pam_cnt; int dep[N]; LL ans;
  char s[N * 2]; int lenl, lenr, n; void clgo(int x){memset(go[x], 0, sizeof(go[x]));} void init(){     fail[0] = pam_cnt = 1;     len[1] = -1;     llast = rlast = 0;     ans = 0;     clgo(0);     clgo(1); } int getLfail(int p){     while(lenl + len[p] + 1 > lenr || s[lenl + len[p] + 1] != s[lenl]) p = fail[p];     return p; } int getRfail(int p){     while(lenr - len[p] - 1 < lenl || s[lenr - len[p] - 1] != s[lenr]) p = fail[p];     return p; } void extend(int type){     int p, c, *last;     if(type == 0) p = getLfail(llast), c = s[lenl] - 'a', last = &llast;     else p = getRfail(rlast), c = s[lenr] - 'a', last = &rlast;     if(!go[p][c]){         int q = ++pam_cnt, now = p;         clgo(q);         len[q] = len[p] + 2;         if(type == 0) p = getLfail(fail[p]);         else p = getRfail(fail[p]);         fail[q] =  go[p][c];         go[now][c] = q;         dep[q] = dep[fail[q]] + 1;         if(lenr - lenl + 1 == len[q]) llast = rlast = q;         else *last = q;     }     else *last = go[p][c];     ans += dep[*last]; }
  int main(){     while(cin >> n){         init();         lenl = n + 1;         lenr = n;         for(int i = 1; i <= n; i++){             int type;             cin >> type;             if(type <= 2){                 string x; cin >> x;                 if(type == 1) s[--lenl] = x[0], extend(0);                 else s[++lenr] = x[0], extend(1);             }             else if(type == 3) cout << pam_cnt - 1 << "\n";             else cout << ans << "\n";         }     }     return 0; }
   | 
 
今天突然意识到一个很严重的问题,双端插入回文串的时间复杂度为啥对呢?原因是最长回文前后缀相等的那个特判,如果不加分析,会破坏均摊分析,所以现在来分析一下双端插入回文串的时间复杂度:
不妨认为插入到后面,插入后左边的最长回文前缀为 $L$ ,右边为 $R$ ,显然 $L$ 是 $R$ 的最长回文真前缀,否则与插入前 $L$ 是最长回文前缀矛盾,所以在回文树上,$L$ 和 $R$ 的深度差恰好为 $1$ ,所以这个操作只会让势能增加 $1$ ,不会破坏均摊分析。
证毕。
去均摊化
可以用于解决后端插入/删除 、 Trie 上 PAM 、可持久化 PAM 的问题。
详情请见 KMP 和 PAM 的可持久化。
在此不再赘述。
前后端插入/删除
PS :同样可以用同样的方法可持久化。