HDU3613 Manacher//EXKMP//KMP

http://acm.hdu.edu.cn/showproblem.php?pid=3613

每个字符都有一个权值,将一个字符串分成两半,如果某一半是回文串就把所有的字符权值加起来,否则当0来处理,问最大值会是多少。

这题很明显是判断前后缀的回文串然后用O(n)的时间遍历取最大值。

问题在于如何判断是前后缀的最大回文串,对于回文串,就很自然而然的想到Manacher算法,对于每一个点,如果以它为中心的回文字符串和最前端接上,他就是一个前缀的回文串,如果和后面接上就是一个后缀的回文串,将所有的这些信息加入到一个数组中之后询问即可。

#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;
}
View Code

当我们想到解决回文串问题的时候将字符串反转,我们就可以考虑用到扩展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的回文后缀。

#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;
}
View Code

虽然KMP算法是比较基础的算法,但是本题kmp算法在我看来是最难想到的。

与EXKMP一样,将字符串反转形成s1,s2之后,用s1作为模式串与s2匹配,当匹配完成后,指向s2的指针一定是处于末尾的,此时指向s1的指针就是与s2的最大前缀匹配,依据next的特性,我们将前缀k往前跳的每一个点也必然是s1的前缀和s2的后缀匹配的点,也就是s的回文前缀长度。

同理,我们处理出s的回文后缀长度即可。

#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;
}
View Code

 

posted @ 2018-08-22 21:26  Hugh_Locke  阅读(244)  评论(0编辑  收藏  举报