HDU 5785 Interesting manacher + 延迟标记

题意:给你一个串,若里面有两个相邻的没有交集的回文串的话,设为S[i...j] 和 S[j+1...k],对答案的贡献是i*k,就是左端点的值乘上右端点的值。

首先,如果s[x1....j]、s[x2....j]、s[x3....j]....s[xn....j]、是回文串,而且s[j+1...y1]、s[j+1...y2]、s[j+1...y3]、也是回文串,那么这些串对答案的贡献一共就是(x1+x2+...+xn)*(y1+y2+....+yn)

所以想到了用cntL[i]表示:以i这个字符为结尾的回文串的左端点之和,cntR[i]表示:以i这个字符为开始的回文串的右端点之和。

所以答案就是for i=1 to lenstr-1  ans += cntL[i]*cntR[i+1];

通俗点说吧,根据manacher,p[i]就表示以i这个字符为中心的回文串的半径长度,所以有s[i-p[i]...i+p[i]]是一个回文串

所以cntL[i+p[i]]就要加上i-p[i],然后因为s[i-p[i]+1...i+p[i]-1]也是一个回文串,所以cntL[i+p[i]-1]就要加上i-p[i]+1等等之类的。所以就是在[i..i+p[i]]这个区间上,依次加上 i、i-1、i-2、.....i-p[i],这样一个公差为-1的等差数列。这个可以用延迟标记来做

先看简单的,在[L,R]上加上一个数val,我们只需要cnt[L] += val; cnt[R+1] -= val;   然后O(n)扫描,即可得到这个数组的所有值。但是现在是等差数列呢?其实是差不多的,只不过要开多一个数组而已。

首先,如果是在[begin,end]上加上一个以val为首相的等差数列,可以这样考虑。首先cnt[begin] += val;然后可以算出结束的时候的值是多少把?cnt[end+1] -= calc_end_val;即可。然后中间的公差,因为可能有叠加现象,我们要开多个数组subL[i]表示这个位置有多少个-1,这个subL[i]就像一开始的最简单的这样传递下去即可。就是subL[begin] += 1; subL[end+1] -= 1;

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;

#include <iostream>
#include <sstream>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include<stack>
#include <string>
const int maxn = 1000000+20;
const int MOD = 1000000007;
int cntL[maxn],cntR[maxn];
int subL[maxn],subR[maxn];
char str[maxn<<1];
int p[maxn<<1];
void addL (int begin,int end,int val)
{
    if (end<begin) return ;
    cntL[begin] += val;
    cntL[end+1] -= begin-end+val;
    subL[begin+1]++;
    subL[end+1]--;

    cntL[begin] %= MOD;
    cntL[end+1] = (cntL[end+1]+MOD)%MOD;
    subL[begin+1]%=MOD;
    subL[end+1]=(subL[end+1]+MOD)%MOD;
    return ;
}
void addR (int begin,int end,int val)
{
    if (end<begin) return ;
    cntR[begin] += val;
    cntR[end+1] -= begin-end+val;
    subR[begin+1]++;//记录有多少个数列覆盖的,记录公差
    subR[end+1]--;

    cntR[begin] %= MOD;
    cntR[end+1] = (cntR[end+1]+MOD)%MOD;
    subR[begin+1]%=MOD;
    subR[end+1]=(subR[end+1]+MOD)%MOD;
    return ;
}
int manacher (char str[],int lenstr)
{
    str[0]='*';
    for (int i=lenstr;i>=0;i--)
    {
        str[i+i+2]=str[i+1];
        str[i+i+1]='#';
    }
    int id=0,maxlen=0;
    for (int i=2;i<=2*lenstr+1;i++)
    {
        if (p[id]+id>i)
        {
            p[i]=min(p[id]+id-i,p[2*id-i]);
        }
        else p[i]=1;
        while (str[i+p[i]] == str[i-p[i]]) ++p[i];
        if (p[id]+id<p[i]+i) id=i;
        maxlen=max(maxlen,p[i]);
    }
    return maxlen-1;
}

void init_arr(int gg)
{
    for (int i=1;i<=gg;++i)
    {
        subL[i] += subL[i-1];
        cntL[i] += cntL[i-1]-subL[i];

        subR[i] += subR[i-1];
        cntR[i] += cntR[i-1]-subR[i];

        subL[i] %= MOD;  subR[i] %= MOD;
        cntL[i]=(cntL[i]+MOD)%MOD; cntR[i]=(cntR[i]+MOD)%MOD;
    }
    return ;
}
void init ()
{
    memset(cntL,0,sizeof cntL); memset(cntR,0,sizeof cntR);
    memset(subL,0,sizeof subL); memset(subR,0,sizeof subR);
    return ;
}
void work ()
{
    init();
    int lenstr = strlen(str+1);
    manacher(str,lenstr);
    for (int i=2;i<=2*lenstr+1;++i)
    {
        int len = p[i]-1;
        if (len==0) continue;
        if (i&1) //'#'所在位置
        {
            int pos = i/2;//截断
            //len必须是偶数
            int t = len/2;
            int begin = pos-t+1; //这些细节要慢慢算啦,慢慢推公式
            int end = t+pos;
            addL(pos+1,end,pos);
            addR(begin,pos,end);
        }
        else //字母所在位置
        {
            int pos = i/2;
            len--;
            len/=2;
            int begin = pos-len;
            int end = pos+len;
            addL(pos,end,pos);
            addR(begin,pos,end);
        }
    }
    init_arr(lenstr);
    LL ans = 0;
    for (int i=1;i<=lenstr-1;++i)
    {
        ans += 1LL*cntL[i]*cntR[i+1];
        ans %= MOD;
    }
    printf ("%I64d
",ans);
    return ;
}
int main ()
{
    #ifdef local
    freopen("data.txt","r",stdin);
    #endif
    while(scanf("%s",str+1)!=EOF) work();
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/liuweimingcprogram/p/5782177.html