tsinsen A1393. Palisection (回文树)

A1393. Palisection
时间限制: 2.0s   内存限制: 256.0MB  
总提交次数: 392   AC次数: 143   平均分: 59.22
将本题分享到:
       
   
试题来源
  CODEFORCES 17E
问题描述
  给你一个长度 n (1 ≤ n ≤ 2·10 6) 的只由小写字母组成的字符串s。
  我们考虑s的所有连续且回文的子串集合P。位置不同但内容相同的两个串算作不同。
  问从P中选出两个串且他们在s中有公共位置的方法数有几个?
输入格式
  第一行一个整数n
  第二行字符串s
输出格式
  一行答案,输出结果对51123987取余。
样例输入
4
babb
样例输出
6
数据规模和约定
  10%的数据 n<=5
  15%的数据 n<=100
  25%的数据 n<=1000
  50%的数据 n<= 2·10 6

反着插一次,计算后缀和,再正着插一次,计算没有重复位置的字符串对,然后用总的减掉就好了

/*======================================================
# Author: whai
#
# Last modified: 2015-10-05 17:03
#
# Filename: tsinsen_A1393.cpp
======================================================*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <set>
#include <map>

using namespace std;

#define LL long long
#define PB push_back
#define P pair<int, int>
#define X first
#define Y second

const int N = 2 * 1e6 + 5;
const int M = 26;
const int MOD = 51123987;

struct PalTree {
	int nxt[N][M]; //表示编号为i的节点表示的回文串在两边添加字符c以后变成的回文串的编号
	int fail[N]; //表示节点i失配以后跳转不等于自身的节点i表示的回文串的最长后缀回文串
	int cnt[N]; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
	int num[N]; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
	int len[N]; //表示编号为i的节点表示的回文串的长度(一个节点表示一个回文串)
	int S[N]; //表示第i次添加的字符(一开始设S[0] = -1(可以是任意一个在串S中不会出现的字符))
	int last; //指向新添加一个字母后所形成的最长回文串表示的节点
	int n; //表示添加的字符个数
	int p; //表示添加的节点个数

	int new_node(int x) {
		memset(nxt[p], 0, sizeof(nxt[p]));
		cnt[p] = 0;
		num[p] = 0;
		len[p] = x;
		return p++;
	}

	void init() {
		p = 0;
		new_node(0);
		new_node(-1);
		last = 0;
		n = 0;
		S[0] = -1;
		fail[0] = 1;
	}

	int get_fail(int x) {
		while(S[n - len[x] - 1] != S[n]) x = fail[x];
		return x;
	}

	int add(int x) {
		x -= 'a';
		S[++n] = x;
		int cur = get_fail(last);
		if(!nxt[cur][x]) {
			int now = new_node(len[cur] + 2);
			fail[now] = nxt[get_fail(fail[cur])][x];
			nxt[cur][x] = now;
			num[now] = num[fail[now]] + 1;
		}
		last = nxt[cur][x];
		++cnt[last];
		return num[last];
	}

	void count() {
		for(int i = p - 1; i >= 0; --i)
			cnt[fail[i]] += cnt[i];
	}

	LL pal_str_num() {
		LL ret = 0;
		for(int i = p - 1; i > 0; --i) {
			cnt[fail[i]] = (cnt[fail[i]] + cnt[i]) % MOD;
			ret = (ret + cnt[i]) % MOD;
		}
		return ret;
	}
};

PalTree pt;
char str[N];
int sum[N];

void gao(int n) {
	LL ans = 0;
	pt.init();
	sum[n] = 0;
	for(int i = n - 1; i >= 0; --i) {
		sum[i] = (sum[i + 1] + pt.add(str[i])) % MOD;
	}
	pt.init();
	for(int i = 0; i < n; ++i) {
		ans = (ans + (LL)pt.add(str[i]) * sum[i + 1]) % MOD;
	}
	LL all = pt.pal_str_num();
	ans = (((LL)all * (all - 1) / 2 % MOD - ans) % MOD + MOD) % MOD;
	cout<<ans<<endl;
}

int main() {
	int n;
	while(scanf("%d", &n) != EOF) {
		scanf("%s", str);
		gao(n);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值