HDU3613 Manacher//EXKMP//KMP
http://acm.hdu.edu.cn/showproblem.php?pid=3613
每个字符都有一个权值,将一个字符串分成两半,如果某一半是回文串就把所有的字符权值加起来,否则当0来处理,问最大值会是多少。
这题很明显是判断前后缀的回文串然后用O(n)的时间遍历取最大值。
问题在于如何判断是前后缀的最大回文串,对于回文串,就很自然而然的想到Manacher算法,对于每一个点,如果以它为中心的回文字符串和最前端接上,他就是一个前缀的回文串,如果和后面接上就是一个后缀的回文串,将所有的这些信息加入到一个数组中之后询问即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <map> #include <set> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <cstdio> #include <cstdlib> #include <cstring> #include <sstream> #include <iostream> #include <algorithm> #include <functional> using namespace std; #define For(i, x, y) for(int i=x;i<=y;i++) #define _For(i, x, y) for(int i=x;i>=y;i--) #define Mem(f, x) memset(f,x,sizeof(f)) #define Sca(x) scanf("%d", &x) #define Scl(x) scanf("%lld",&x); #define Pri(x) printf("%d\n", x) #define Prl(x) printf("%lld\n",x); #define LL long long #define ULL unsigned long long const int maxn = 5e5 + 10; const int INF = 0x3f3f3f3f; int N,M,tmp,K; int value[30]; char str[maxn]; char Ma[maxn * 2]; int Mp[maxn * 2]; bool cut[maxn][2]; LL sum[maxn]; void Manacher(char *x,int n){ int l = 0; Ma[l++] = '$'; Ma[l++] = '#'; for(int i = 0 ; i < n ; i ++){ Ma[l++] = x[i]; Ma[l++] = '#'; } int mx = 0,id = 0; for(int i = 0 ; i < l ; i ++){ Mp[i] = mx > i?min(mx - i,Mp[2 * id - i]):1; while(i + Mp[i] < l && Ma[i + Mp[i]] == Ma[i - Mp[i]]) Mp[i]++; if(Mp[i] + i > mx){ mx = Mp[i] + i; id = i; } } } int main() { int T; scanf("%d",&T); while(T--){ Mem(cut,0); For(i,0,25) Sca(value[i]); scanf("%s",str); int len = strlen(str); Manacher(str,len); sum[0] = 0; for(int i = 1; i <= len; i ++) sum[i] = sum[i - 1] + value[str[i - 1] - 'a']; for(int i = 2 ; i < len * 2 + 1; i ++){ if(i - Mp[i] == 0) cut[Mp[i] - 1][0] = 1; if(i + Mp[i] == len + len + 2) cut[Mp[i] - 1][1] = 1; } LL ans = -99999999; For(i,1,len - 1){ LL s = 0; if(cut[i][0]) s += sum[i]; if(cut[len - i][1]) s += sum[len] - sum[i]; ans = max(ans,s); } Prl(ans); } return 0; }
当我们想到解决回文串问题的时候将字符串反转,我们就可以考虑用到扩展KMP算法了。
将原字符串s1进行反转之后得到的s2,当s1作为模式串,s2作为主串进行匹配时,得到的是s2的所有后缀与s1的前缀的最大匹配值,也就是说明当i == extend1[l - i]的时候,s1的前i个字符和s1的后i个字符相等,也就是说前i个字符是s的回文前缀。
同理,当s2作为模式串与s1匹配的时候,得到的是s1的所有后缀与s2的前缀的最大匹配值,当extend2[] == l - i 的时候就是s的回文后缀。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <map> #include <set> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <cstdio> #include <cstdlib> #include <cstring> #include <sstream> #include <iostream> #include <algorithm> #include <functional> using namespace std; #define For(i, x, y) for(int i=x;i<=y;i++) #define _For(i, x, y) for(int i=x;i>=y;i--) #define Mem(f, x) memset(f,x,sizeof(f)) #define Sca(x) scanf("%d", &x) #define Scl(x) scanf("%lld",&x); #define Pri(x) printf("%d\n", x) #define Prl(x) printf("%lld\n",x); #define CLR(u) for(int i=0;i<=N;i++)u[i].clear(); #define LL long long #define ULL unsigned long long #define mp make_pair #define PII pair<int,int> #define PIL pair<int,long long> #define PLL pair<long long,long long> #define pb push_back #define fi first #define se second #define Vec Point typedef vector<int> VI; const double eps = 1e-9; const int maxn = 5e5 + 10; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; int N,M,tmp,K; int value[30]; char s1[maxn]; char s2[maxn]; int next1[maxn],next2[maxn]; int extend1[maxn],extend2[maxn]; bool cut[maxn][2]; LL sum[maxn]; void EKMP_Pre(char x[],int m,int *next){ int j = 0; next[0] = m; while(j + 1 < m && x[j] == x[j + 1]) j ++; next[1] = j; int k = 1; for(int i = 2; i < m ; i++){ int p = next[k] + k - 1; int l = next[i - k]; if(i + l - 1 < p){ next[i] = l; }else{ int j = max(0,p - i + 1); while(i + j < m && x[i + j] == x[j]) j ++; k = i; next[i] = j; } } } void EKMP(char x[],int m,char y[],int n,int *next,int *extend){ EKMP_Pre(x,m,next); int j = 0; while(x[j] == y[j]) j ++; extend[0] = j; int k = 0; for(int i = 1 ; i < n ; i ++){ int p = extend[k] + k - 1; int l = next[i - k]; if(l + i < p + 1) extend[i] = l; else{ int j = max(0,p - i + 1); while(i + j < n && j < m && y[i + j] == x[j]) j++; k = i; extend[i] = j; } } } int main() { int T; Sca(T); while(T--){ Mem(cut,0); For(i,0,25) Sca(value[i]); scanf("%s",s1); int l = strlen(s1); for(int i = 0 ; i < l ; i ++) s2[l - i - 1] = s1[i]; EKMP(s1,l,s2,l,next1,extend1); EKMP(s2,l,s1,l,next2,extend2); for(int i = 1; i <= l ; i ++) sum[i] = sum[i - 1] + value[s1[i - 1] - 'a']; LL ans = 0; for(int i = 1; i < l; i ++){ LL s = 0; if(extend1[l - i] == i) s += sum[i]; if(extend2[i] == l - i) s += sum[l] - sum[i]; ans = max(ans,s); } Prl(ans); } #ifdef VSCode system("pause"); #endif return 0; }
虽然KMP算法是比较基础的算法,但是本题kmp算法在我看来是最难想到的。
与EXKMP一样,将字符串反转形成s1,s2之后,用s1作为模式串与s2匹配,当匹配完成后,指向s2的指针一定是处于末尾的,此时指向s1的指针就是与s2的最大前缀匹配,依据next的特性,我们将前缀k往前跳的每一个点也必然是s1的前缀和s2的后缀匹配的点,也就是s的回文前缀长度。
同理,我们处理出s的回文后缀长度即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <map> #include <set> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <cstdio> #include <cstdlib> #include <cstring> #include <sstream> #include <iostream> #include <algorithm> #include <functional> using namespace std; #define For(i, x, y) for(int i=x;i<=y;i++) #define _For(i, x, y) for(int i=x;i>=y;i--) #define Mem(f, x) memset(f,x,sizeof(f)) #define Sca(x) scanf("%d", &x) #define Scl(x) scanf("%lld",&x); #define Pri(x) printf("%d\n", x) #define Prl(x) printf("%lld\n",x); #define CLR(u) for(int i=0;i<=N;i++)u[i].clear(); #define LL long long #define ULL unsigned long long #define mp make_pair #define PII pair<int,int> #define PIL pair<int,long long> #define PLL pair<long long,long long> #define pb push_back #define fi first #define se second #define Vec Point typedef vector<int> VI; const double eps = 1e-9; const int maxn = 5e5 + 10; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; int N,M,tmp,K; int value[30]; char s1[maxn]; char s2[maxn]; int Next[maxn]; LL sum[maxn]; int cut[maxn][2]; void KMP_Pre(char x[],int m){ int i = 0,j = Next[0] = -1; while(i < m){ while(j != -1 && x[i] != x[j]) j = Next[j]; Next[++i] = ++j; } } int KMP(char x[],int m,char y[],int n){ KMP_Pre(x,m); int i = 0,j = 0; while(i < m && j < n){ while(j != -1 && y[i] != x[j]) j = Next[j]; i++,j++; } return j; } int main() { int T; Sca(T); while(T--){ Mem(cut,0); For(i,0,25) Sca(value[i]); scanf("%s",s1); int l = strlen(s1); for(int i = 0 ; i < l ; i ++) s2[l - i - 1] = s1[i]; for(int i = 1; i <= l ; i ++) sum[i] = sum[i - 1] + value[s1[i - 1] - 'a']; int k = KMP(s1,l,s2,l); while(k) cut[k][0] = 1,k = Next[k]; k = KMP(s2,l,s1,l); while(k) cut[k][1] = 1,k = Next[k]; LL ans = -INF; for(int i = 1; i < l; i ++){ LL s = 0; if(cut[i][0]) s += sum[i]; if(cut[l - i][1]) s += sum[l] - sum[i]; ans = max(ans,s); } Prl(ans); } #ifdef VSCode system("pause"); #endif return 0; }