HDU 3613 Best Reward(EXkmp+枚举 / Manacher+枚举)

题目链接http://acm.hdu.edu.cn/showproblem.php?pid=3613

题目大意:一条字符串切成两部分,如果是回文的要计算总价值,不是回文价值就为0

注意的两组数据:


Input

1 1 1......

a

Output

0


Input

-1 1 1......

aaaaaa

Output

-6(虽然AC代码输出0也对)


解题思路一:

利用反串对原串exkmp判断原串所有后缀是否回文

利用正串对反串exkmp判断反串所有后缀是否回文进而判断原串对应位置前缀是否回文。

这里的一个结论是

求出exkmp的extend数组, extend[i] ==  后缀长度    \Leftrightarrow   后缀回文

证明:我们凭借自己的认知可以判断extend[i] = 后缀长度/2 (eg:7/2=3,8/2=4)就是回文了,一定要相等吗?这样会不会少考虑情况?

其实两种说法是等价的,当已匹配部分超过后缀长度/2,那么一定会完全匹配。

你看:

然后记得答案初始化为负无穷,不要为0,因为可能取不到0。

然后枚举。(先处理出前缀和,后缀和)

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define INF 0x3f3f3f3f
#define idx(x) x-96

const int N = 5e5+5;

int nt[N],ex[N];
bool flag[2][N];
char s[N],ss[N];
int suf[N],pre[N];
int val[27];

void GETNEXT(char *s,int len)
{
    int i=0,j,po;
    nt[0] = len;
    while (i+1<len && s[i]==s[i+1]) i++;
    nt[1] = i;
    po = 1;
    for0(i,2,len){
        if (po+nt[po]-i > nt[i-po])
            nt[i] = nt[i-po];
        else {
            j = po+nt[po]-i;
            if (j<0) j = 0;
            while (i+j<len && s[i+j]==s[j]) j++;
            nt[i] = j;
            po = i;
        }
    }
}

void EXKMP(char *s,char *ss,int len,int sign)
{
    int i=0,j,po;
    GETNEXT(ss,len);
    while (i<len && s[i]==ss[i]) i++;
    ex[0] = i;
    po = 0;
    for0(i,1,len){
        if (po+ex[po]-i > nt[i-po])
            ex[i] = nt[i-po];
        else {
            j = po+ex[po]-i;
            if (j<0) j = 0;
            while (i+j<len && s[i+j]==ss[j]) j++;
            ex[i] = j;
            po = i;
        }
    }

    if (sign==0){///后缀
        for0(i,0,len) if (ex[i] == len-1 - i + 1) flag[0][i] = true;
    }
    else {///前缀
        for0(i,0,len) if (ex[i] == len-1 - i + 1) flag[1][len-1-i+1-1] = true;
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while (T--){
        memset(flag,false,sizeof flag);
        for1(i,1,26) scanf("%d",val+i);

        scanf("%s",s);
        int len = strlen(s);
        ///求前缀和
        pre[0] = val[idx(s[0])];
        for0(i,1,len){
            pre[i] = pre[i-1]+val[idx(s[i])];
        }
        ///求反转字符串+求后缀和
        suf[len] = 0;
        for (int i=len-1;i>=0;i--){
            suf[i] = suf[i+1] + val[idx(s[i])];
            ss[len-1-i] = s[i];
        }
        ss[len] = '\0';
        //puts(ss);
        EXKMP(s,ss,len,0);///判断正串后缀是否回文
        EXKMP(ss,s,len,1);///判断反串后缀是否回文,并转化为判断原串对应点的前缀是否回文
/*
        for0(i,0,len){
            if (flag[0][i]) printf("%d可做后缀 ",i);
            if (flag[1][i]) printf("%d可做前缀\n",i);
        }
*/
        int ans = -INF;

        ///开始枚举所有切开的情况
        for0(i,0,len-2){
            if (flag[1][i] && flag[0][i+1]) ans = max(ans,pre[i]+suf[i+1]);
            if (flag[1][i] && !flag[0][i+1]) ans = max(ans,pre[i]+0);
            if (!flag[1][i] && flag[0][i+1]) ans = max(ans,0+suf[i+1]);
            if (!flag[1][i] && !flag[0][i+1]) ans = max(ans,0);
        }

        printf("%d\n",ans);
    }
    return 0;
}

解题思路二:

核心思路还是利用manacher判断出前后缀是否回文。

现在看当时的代码写的挺蠢的(可能以后看上面的也会),你懂思路就好,我懒得再改了。

manacher预处理求出Len数组,然后枚举出所有左界为1或者右界为n的回文串(这样)

然后每种情况计算一下对应的价值总和,取最大值

这样我们可以求出所有①回文串+0(另一段不是回文)②回文串+回文串(另一半也是回文)

由第二组数据看出,我们不能保证每个串切成两段都不是回文的情况,因此答案不能初始化为0,先初始化为无穷小,

然后O(N)判断一下是否每个点切开来都是两个回文串,存在一组两个都不是回文的,答案就可以先更新为0

代码:

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define for0(i,a,b) for (int i=a;i<b;i++)
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define pt(x,y) printf("%s  =  %d\n",#x,y)
using namespace std;

const int N = 5e5+5;

char s[N],tmp[N<<1];
int Len[N<<1];

int INIT(char *st)
{
    int i,len=strlen(st);//pt("len",len);
    tmp[0]='@';
    for(i=1;i<=2*len;i+=2)
    {
        tmp[i]='#';
        tmp[i+1]=st[i/2];
    }
    tmp[2*len+1]='#';
    tmp[2*len+2]='$';
    tmp[2*len+3]=0;
    return 2*len+1;
}

int MANACHER(char *st,int len)
{
     int mx=0,ans=0,po=0;
     for(int i=1;i<=len;i++)
     {
         if(mx>i)
         Len[i]=min(mx-i,Len[2*po-i]);
         else
         Len[i]=1;
         while(st[i-Len[i]]==st[i+Len[i]])
         Len[i]++;
         if(Len[i]+i>mx)
         {
             mx=Len[i]+i;
             po=i;
         }
         ans=max(ans,Len[i]);
     }
     return ans-1;
}

struct node
{
    int l_or_r;///回文是左边一半还是右边一半,左1,右2
    int bank;///左边一半就是回文右边界,右一半就是左边界
};
vector<node>v;
int ans;
int val[30],presum[N],sufsum[N];///价值,前缀和,后缀和
#define idx(i) i-96

int main()
{
#define DEBUG
#ifdef DEBUG
    //freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
    //freopen("C:/Users/DELL/Desktop/output.txt", "w", stdout);
#endif
    int T;
    scanf("%d",&T);
    while (T--){
        ans = -50000010;///初始答案设置为无穷小
        memset(presum,0,sizeof presum);
        memset(sufsum,0,sizeof sufsum);
        //memset(Len,0,sizeof Len);
        v.clear();
        for1(i,1,26) scanf("%d",val+i);

        scanf("%s",s);
        int len = strlen(s);
        if (len==1){///只有一个字符的情况特判
            puts("0");
            continue;
        }

        for (int i=1;i<=len;i++) presum[i] = presum[i-1]+val[idx(s[i-1])];
        for (int i=len;i>=1;i--) sufsum[i] = sufsum[i+1]+val[idx(s[i-1])];

        int llen = INIT(s);
        MANACHER(tmp,llen);
        
        ///判断是否没有都不是回文的情况
        for (int i=1;i<=len-1;i++){///[1,i]为左,[i+1,len]为右,判断是否怎样分都是回文
            if (Len[i+1]-1!=i && Len[len+i+1]-1!=len-i) {ans = 0;break;}///存在两段都不是回文的话,ans=0可以做到
        }

        for1(i,1,len){
            int mid_len = Len[2*i]-1;///XXIXX
            int lmid_len = Len[2*i+1]-1;///XXIXXX
            int rmid_len = Len[2*i-1]-1;///XXXIXX
            if (mid_len>0 && mid_len<len){
                if (i-mid_len/2==1) v.push_back({1,mid_len});
                if (i+mid_len/2==len) v.push_back({2,len+1-mid_len});
            }
            if (lmid_len>0 && lmid_len<len){
                if (i-lmid_len/2+1==1) v.push_back({1,lmid_len});
                if (i+lmid_len/2==len) v.push_back({2,len+1-lmid_len});
            }
            if (rmid_len>0 && rmid_len<len){
                if (i-rmid_len/2==1) v.push_back({1,rmid_len});
                if (i+rmid_len/2-1==len) v.push_back({2,len+1-rmid_len});
            }
        }

        for (auto now:v){
        //for (int i=0;i<v.size();i++){node now = v[i];
            int nowans = 0;
            if (now.l_or_r==1){///当前回文是左边一块
                nowans = presum[now.bank];
                int st = now.bank+1,ed = len;
                int l = ed-st+1;
                //pt("l",l);
                if (Len[st+ed]-1==l){
                        nowans += sufsum[st];
                }
            }
            else {///当前回文是右边一块
                nowans = sufsum[now.bank];
                int st = 1,ed = now.bank-1;
                int l = ed-st+1;
                if (Len[st+ed]-1==l){
                        nowans += presum[ed];
                }
            }

            ans = max(ans,nowans);
        }

        printf("%d\n",ans);
    }
    return 0;
}

总结:

①exkmp:在处理回文问题方面,exkmp能判断前后缀是否回文,针对此类问题exkmp比较方便,清晰.

但是exkmp面对不是前缀也不是后缀的情况判断回文就无能为力了。(也许有但我还不会)

②manacher可以判断任意回文,但是要分类,做起来可能没有exkmp判断前后缀那么形象。Len数组用起来真的烦!(也许事实上方便但我还不会)

事实上manacher判断一段区间是否回文判断  Len[ed-st]-1==ed-st+1即可emm,所以两种方法应该是差不多的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值