poj3376 字典树 + 扩展kmp

http://vjudge.net/contest/70325#problem/T

kuangbin专题十六字最后一发

题意:n个字符串,每个字符串长度为l,将字符串两两自由组合一共有(n * n种方案)判断其中组合之后为回文串的字符有多少个.

做法:首先由两个字符串a,b.要使它们能组成回文串有三种情况

1.alen < blen 时

a是b的反串前缀,且b的剩余部分可以认为是后缀是回文串

2.alen > blen

b的反串是a的前缀,且a的后缀是回文串

3.alen == blen

b是a的反串

现将所有字符串建立在字典树中。然后求s和s的反串的后缀中是回文串的位置,也就是说从哪个位置开始一直到最后是回文串实现就是(i + extend[i] == n,以正串为例,反串的首位就是正串的最后一位相等 )。用字典树中的参数huiwen记录从该字母开始回文串的个数,注意回文记录的只是以正串为后缀的个数

另外:因为只给了字符串的总长度,所以,只开一维的字符串数组,每次接着上次字符串结束的位置开始即可。为了方便,记录字符串开始的位置,和结束的位置

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<map>
#include<algorithm>
using namespace std;
const int MAXN=2000007;
long long ans = 0;
bool flag[2][MAXN];
int nexta[MAXN];
int extend[MAXN];
void EKMP(char s[],char t[],int bg,int ed,int sign)//s主串,t模式串
{

     nexta[bg] = ed - bg;
    int k;
    for(k = 0; k + 1 + bg< ed && t[k + bg] == t[k + 1 + bg]; k ++ )
    {
    }
    nexta[bg + 1] = k;
    k = bg + 1;
    int p,L,j;
    for(int i = 2 + bg; i < ed; i ++)
    {
        p = k + nexta[k] - 1, L = nexta[bg + i - k];
        if(i + L <= p)
        {
            nexta[i] = L;
        }
        else
        {
            j = p - i + 1;
            if(j < 0)
                j = 0;
            while(i + j < ed && t[i + j] == t[bg + j])
            {
                j++;
            }
            nexta[i] = j;
            k = i;
        }

    }
    for(k = 0; k+ bg < ed && s[k+bg] == t[k+bg]; k ++ )
    {
    }
    extend[bg] = k;
    k = bg;
    for(int i = bg + 1; i < ed; i ++)
    {
        p = k + extend[k] - 1, L = nexta[bg + i - k];//注意p是加extend
        if(i + L <= p)
        {
            extend[i] = L;
        }
        else
        {
            int j = p - i + 1;
            if(j < 0)
                j = 0;
            while(i + j < ed &&j < ed && s[i + j] == t[bg + j])
            {
                j++;
            }
            extend[i] = j;
            k = i;
        }

    }
    for(int i = bg; i < ed; i ++)
    {
        if(extend[i] + i ==  ed)
        {
            flag[sign][i] = true;
        }
    }
}
struct node
{
    int nexta[26];
    int countx;
    int huiwen;

} f[2000006];
void init(int a)//新增函数
{
    memset(f[a].nexta,-1,sizeof(f[a].nexta));
    f[a].countx = 0;
    f[a].huiwen = 0;
}
int cnt = 1;
void build(char a[],int bg,int ed)
{
    int p = 0;
    int t;
    for(int i = bg; i < ed; i++)
    {

        t = a[i] - 'a';
        if(flag[0][i] == true)
        {
            f[p].huiwen ++;
        }
        if(f[p].nexta[t] == -1)
        {
            f[p].nexta[t] = cnt;
            init(cnt);
            cnt++;
        }

        p = f[p].nexta[t];

    }
    f[p].countx ++;//单词相同的个数。hud1251则是前缀相同的个数
}
void findx(char a[],int bg,int ed)
{
    int k = 0;
    int t;
    for(int i = bg; i < ed; i ++)
    {
        t = a[i] - 'a';
        k = f[k].nexta[t];
        if(k == -1)
            break;
        if((i + 1< ed && flag[1][i + 1] == true)||i + 1 == ed)//如果两个字符串是完全相等的则满足i + 1 == ed的条件,又因为以k结尾的字符有两个,一下子加上了两个。且此时k != -1
        {
            ans += f[k].countx;//表示以k结束的字符串的个数
        }

    }
    if(k != -1)//反串比正串短的情况,要加上正串后面都是回文串的情况
    {
        ans += f[k].huiwen;
    }
    //注意因为是要匹配n * n次,保证不重不漏。
    return;
}
char s[MAXN],ins[MAXN];
int bg[MAXN],ed[MAXN];

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    memset(nexta,0,sizeof(nexta));
    memset(extend,0,sizeof(extend));
    init(0);
    memset(flag,false,sizeof(flag));
    int T,n;
    int L = 0;
    scanf("%d",&T);
    for(int t = 0; t < T; t++)
    {
        scanf("%d%s",&n,s + L);
        for(int i = 0; i < n; i++)
        {
            ins[i + L] = s[L + n - 1 - i];
        }

        bg[t] = L;
        ed[t] = L + n;
        L += n;
        EKMP(s,ins,bg[t],ed[t],0);
        EKMP(ins,s,bg[t],ed[t],1);
        build(s,bg[t],ed[t]);
    }
    for(int i = 0; i < T; i ++)
    {
        findx(ins,bg[i],ed[i]);
    }
    cout<<ans<<endl;
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值