P1872 回文串计数(回文树)

题目描述

小a虽然是一名理科生,但他常常称自己是一名真正的文科生。不知为何,他对于背诵总有一种莫名其妙的热爱,这也促使他走向了以记忆量大而闻名的生物竞赛。然而,他很快发现这并不能满足他热爱背诵的心,但是作为一名强大的OIER,他找到了这么一个方法——背诵基因序列。然而这实在是太困难了,小啊感觉有些招架不住。不过他发现,如果他能事先知道这个序列里有多少对互不相交的回文串,他或许可以找到记忆的妙法。为了进一步验证这个想法,小a决定选取一个由小写字母构成的字符串SS来实验。由于互不相关的回文串实在过多,他很快就数晕了。不过他相信,在你的面前这个问题不过是小菜一碟。

(1)对于字符串SS,设其长度为Len,那么下文用Si表示SS中第i个字符(1<=i<=Len)。

(2)S[i,j]表示SS的一个子串,S[i,j]="SiSi+1Si+2...Sj-2Sj-1Sj",比如当SS为"abcgfd"时,S[2,5]="bcgf",S[1,5]="abcgf"。

(3)当一个串被称为一个回文串当且仅当将这个串反写后与原串相同,如“abcba”。

(4)考虑一个四元组(l,r,L,R),当S[l,r]和S[L,R]均为回文串时,且满足1<=l<=r<=L<=R<=Len时,我们称S[l,r]和S[L,R]为一对互不相交的回文串。即本题所求,也即为这种四元组的个数。两个四元组相同当且仅当对应的l,r,L,R都相同。

输入输出格式

输入格式:

输入仅一行,为字符串SS,保证全部由小写字母构成,由换行符标志结束。

50%的数据满足SS的长度不超过200;

100%的数据满足SS的长度不超过2000。

输出格式:

仅一行,为一个整数,表示互不相关的回文串的对数。

输入输出样例

输入样例#1: 复制

aaa

输出样例#1: 复制

5

说明

【样例数据说明】

SS="aaa",SS的任意一个字符串均为回文串,其中总计有5对互不相关的回文串:

(1,1,2,2),(1,1,2,3),(1,1,3,3),(1,2,3,3),(2,2,3,3)。


题解

在这里我们对回文树引入一个新的标记,dep
它表示的是当前这个节点为回文串时,以它结尾的串中包含的回文串的数量
好,那么接下来的意思就很简单了。
我们只需要依次递推过去,先更新出原本顺序上每一位的回文串的数量,再更新出倒序上每一位的回文串的数量(其实倒序就是求尾部的回文串数量)。
这样我们每更新一位,就加上之前每一位的回文串的数量。再乘以下一位的按倒序处理出来的回文串的数量就ok了。


代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int tot;
ll ans,p1[20001],p2[20001];
struct node{
    int fail,ch[26],len,cnt,dep;
}t[200001];
char s[200001];

int read()
{
    int x=0,w=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*w;
}

void solve1()
{
    int len=strlen(s+1),k=0;s[0]='#';
    t[0].fail=t[1].fail=1;t[1].len=-1;tot=1;
    for(int i=1;i<=len;i++)
    {
        while(s[i-t[k].len-1]!=s[i])k=t[k].fail;
        if(!t[k].ch[s[i]-'a']){
            t[++tot].len=t[k].len+2;
            int j=t[k].fail;
            while(s[i-t[j].len-1]!=s[i])j=t[j].fail;
            t[tot].fail=t[j].ch[s[i]-'a'];
            t[k].ch[s[i]-'a']=tot;
            t[tot].dep=t[t[tot].fail].dep+1;
        }
        k=t[k].ch[s[i]-'a'];
        p1[i]=t[k].dep;
        t[k].cnt++;
    }
}

void solve2()
{
    int len=strlen(s+1),k=0;s[0]='#';
    t[0].fail=t[1].fail=1;t[1].len=-1;tot=1;
    for(int i=1;i<=len;i++)
    {
        while(s[i-t[k].len-1]!=s[i])k=t[k].fail;
        if(!t[k].ch[s[i]-'a']){
            t[++tot].len=t[k].len+2;
            int j=t[k].fail;
            while(s[i-t[j].len-1]!=s[i])j=t[j].fail;
            t[tot].fail=t[j].ch[s[i]-'a'];
            t[k].ch[s[i]-'a']=tot;
            t[tot].dep=t[t[tot].fail].dep+1;
        }
        k=t[k].ch[s[i]-'a'];
        t[k].cnt++;
        p2[len-i+1]=t[k].dep;
    }
}

int main()
{
    scanf("%s",s+1);
    int len=strlen(s+1);
    solve1();
    reverse(s+1,s+len+1);
    memset(t,0,sizeof(t));
    solve2();
    for(int i=1;i<=len;i++)p1[i]+=p1[i-1];
    for(int i=1;i<=len;i++)ans+=p1[i]*p2[i+1];
    printf("%lld",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/hhh1109/p/9246004.html

时间: 2024-10-03 15:48:12

P1872 回文串计数(回文树)的相关文章

落谷P1872 回文串计数(回文树)

传送门 这道题显然可以用PAM做出来. PAM可以算出以字符串的第ii个字符为结尾的回文子串的个数.我们将其存到一个数组l[n],再求一个前缀和就可以把字符串的前i个字符的前缀有多少个回文子串求出来. 然后,我们将PAM清空,倒着做一遍,就可以求出以第i个字符为左端点的回文子串个数r[i].与它不相交的回文子串且在它前面的子串有l[i - 1]个,相乘再累加就是答案. 此题在落谷的评级是绿,那是因为此题数据范围只有2000,不用PAM也可以做.但此题可以当做PAM入门的练手题. #include

【NOIP模拟赛】【乱搞AC】【奇技淫巧】【乘法原理】回文串计数

回文串计数 (calc.pas/calc.c/calc.cpp) [题目描述] 虽然是一名理科生,Mcx常常声称自己是一名真正的文科生.不知为何,他对于背诵总有一种莫名的热爱,这也促使他走向了以记忆量大而闻名的生物竞赛.然而,他很快发现这并不能满足他热爱背诵的心,但是作为一名强大的Boer,他找到了这么一条自虐的方式--背诵基因序列.不过这实在是太虐心了,就连Mcx也有些招架不住.不过他发现,如果他能事先知道这个序列里有多少对互不相交的回文串,他或许可以找到记忆的妙法.为了进一步验证这个方法,M

BZOJ 3676 [Apio2014]回文串(回文树)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3676 [题目大意] 考虑一个只包含小写拉丁字母的字符串s. 我们定义s的一个子串t的"出现值"为t在s中的出现次数乘以t的长度. 求s的所有回文子串中的最大出现值. [题解] 我们对给出串建立回文树,统计每个回文串出现次数和长度,相乘取组大即可 [代码] #include <cstdio> #include <algorithm> #include

P3649 [APIO2014]回文串(回文树)

题目描述 给你一个由小写拉丁字母组成的字符串 ss .我们定义 ss 的一个子串的存在值为这个子串在 ss 中出现的次数乘以这个子串的长度. 对于给你的这个字符串 ss ,求所有回文子串中的最大存在值. 输入输出格式 输入格式: 一行,一个由小写拉丁字母(a~z)组成的非空字符串 ss . 输出格式: 输出一个整数,表示所有回文子串中的最大存在值. 输入输出样例 输入样例#1: 复制 abacaba 输出样例#1: 复制 7 输入样例#2: 复制 www 输出样例#2: 复制 4 说明 [样例解

回文串[APIO2014](回文树)

题目描述 给你一个由小写拉丁字母组成的字符串 s.我们定义 s 的一个子串的存在值为这个子串在 s中出现的次数乘以这个子串的长度.对于给你的这个字符串 s,求所有回文子串中的最大存在值. 输入格式 一行,一个由小写拉丁字母(a~z)组成的非空字符串 s. 输出格式 输出一个整数,表示所有回文子串中的最大存在值. 样例 输入样例 1 abacaba 输出样例 1 7 输入样例 2 www 输出样例 2 4 首先建出s的回文树,然后对于每一个回文子串,记录cnt为它出现的次数. 对于它fail树上的

BZOJ2565: 最长双回文串(回文树)

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2565 记录一下每个点往前最长延伸位置,正反两遍,枚举分割点. #include<cstring> #include<iostream> #include<algorithm> #include<cstdio> #define rep(i,l,r) for (int i=l;i<=r;i++) #define down(i,l,r) for (int

P3649 [APIO2014]回文串(回文自动机)

回文自动机裸题,把PAM建出来以后对每个节点更新答案即可 代码: #include <bits/stdc++.h> #define int long long #define sc(a) scanf("%lld",&a) #define scc(a,b) scanf("%lld %lld",&a,&b) #define sccc(a,b,c) scanf("%lld %lld %lld",&a,&

Openjudge-计算概论(A)-回文串判断

描述: 任意给定一个非空的字符串,判断其是否是回文串.回文串是指正向看和反向看均相等的串,如AbcDcbA和cDDc.如果是回文串,则输出1,否则,输出0 输入长度不小于1不大于100的字符串输出如果是回文串,输出1如果不是回文串,输出0 样例输入 abcdefghijkjihgfedcba 样例输出 1思路:这题很简单,算是字符串入门题,只要判断是否从前往后扫和从后往前扫一样就得了,输出.提示:这题输入一定要用gets()函数,否则会报错代码如下: 1 #include<stdio.h> 2

BZOJ 3676 【APIO2014】 回文串

题目链接:回文串 我终于也会回文自动机辣! 其实吗--我觉得回文自动机(听说这玩意儿叫\(PAM\))还是比较\(simple\)的--至少比\(SAM\)友善多了-- 所谓回文自动机,每个节点就代表一个回文串.回文自动机的每个节点有两个东西,一个是\(next\),一个是\(fail\).\(next_{u,x}\)指向节点\(u\)所代表的回文串在两端各添加一个字符\(x\)得到一个新的回文串.\(fail_u\)则指向\(u\)这个节点的最长后缀回文串(不包括本身).当然还有一个\(len