Finding Palindromes POJ - 3376 扩展kmp 字典树

题解

题目大意 n个字符串两两组合问拼起来的串有多少个回文串

将所给字符串存储起来 注意常数不能太大 使用扩展kmp计算反转串的每个后缀和原串的最长公公前缀 插入字典树时标记结束节点和当前位置为后缀是回文的节点
全部插入完毕后再匹配正向串 匹配时如果正向串下一个位置的后缀是回文串则加上当以当前节点结束的反转串数量 当正向串结束加上以当前节点结束的反向串和下一个位置后缀是回文的反向串

AC代码

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const int MAXN = 2e6 + 10;
const int MAXC = 26;
char s[MAXN], rev[MAXN];
int exnex[MAXN]; //扩展next数组
int exten[MAXN]; //s从i为起点的后缀与p的最长公共前缀
int nex[MAXN][MAXC], sed[MAXN], idx; //每个字符连接的子节点编号 满足当前前缀的串数 以当前节点为结尾的串数 末尾节点
int pla[MAXN];
int len[MAXN]; //常数优化

void getexnext(const char p[], int m)
{
	int i = 0, j;
	exnex[0] = m; //0号位为长度
	while (i + 1 < m && p[i] == p[i + 1]) //计算第一项
		i++;
	exnex[1] = i;
	int k = 1 + i, po = 1; //k最远到达点 po最远到达点的起点
	for (i = 2; i < m; i++)
	{
		if (exnex[i - po] < k - i) //因k~po == 0~k-po 则i~k == i-po~k-po(取前者后一部分)
			exnex[i] = exnex[i - po]; //如果exnet[i-po]不足k-i长度则s[i - po + exnet[i-po]] != s[i + exnet[i-po]]
		else
		{
			j = k - i;//如果长度相等或更长因k之后为未探索区域 暂定为k - i之后继续匹配
			if (j < 0) //如果i本身就超过k
				j = 0;
			while (i + j < m && p[i + j] == p[j])
				j++;
			exnex[i] = j;
			k = i + j, po = i; //更新记录
		}
	}
}
void exkmp(const char s[], const char p[], int n, int m)
{
	getexnext(p, m); //计算exnext
	int i = 0, j;
	while (s[i] == p[i] && i < m && i < n) //计算第零项
		i++;
	exten[0] = i;
	int k = i, po = 0;
	for (i = 1; i < n; i++) //从1开始计算
	{
		if (exnex[i - po] < k - i)
			exten[i] = exnex[i - po]; //exten
		else
		{
			j = k - i;
			if (j < 0)
				j = 0;
			while (i + j < n && j < m && s[i + j] == p[j]) //n m s
				j++;
			exten[i] = j; //exten
			k = i + j, po = i;
		}
	}
}
inline int GetID(char c)  //字符映射
{
	return c - 'a'; //简单的将字符减去'a' 根据需求改变
}
void Insert(char s[], int n) //字典树内插入一个字符串s长度为n
{
	int x = 0; //当前节点
	for (int i = 0; i < n; i++) //串下标
	{
		int c = GetID(s[i]); //转换字符为id
		if (!nex[x][c])
			nex[x][c] = ++idx; //新增一个节点
		if (exten[i] == n - i) //当前位置后缀为回文
			pla[x]++; //前一个节点++
		x = nex[x][c]; //移动节点
	}
	sed[x]++; //记录经过一次并记录在此处结束 
}
int Match(const char *s, int n)
{
	int x = 0;
	int res = 0;
	for (int i = 0; i < n; i++)
	{
		int c = GetID(s[i]);
		if (exten[i] == n - i) //下一个位置的后缀是回文
			res += sed[x]; //加在当前节点结束的数量
		if (!nex[x][c]) //不存在节点
		{
			x = -1; //标记失配
			break;
		}
		x = nex[x][c]; //移动节点
	}
	if (x != -1)
		res += sed[x] + pla[x]; //已当前节点为结尾或下一个节点后缀为回文
	return res;
}

int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int N;
	cin >> N;
	for (int i = 1; i <= N; i++)
	{
		int n;
		scanf("%d%s", &n, s + len[i - 1]);
		len[i] = len[i - 1] + n;
		char *str = s + len[i - 1];
		memcpy(rev, str, n); //拷贝指定长度的字符串
		rev[n] = 0;
		reverse(rev, rev + n);
		getexnext(str, n); //反转串扩展kmp计算后缀是否为回文
		exkmp(rev, str, n, n);
		Insert(rev, n); //插入字典树并进行标记
	}
	ll ans = 0;
	for (int i = 1; i <= N; i++) //插入完毕后在匹配
	{
		char *str = s + len[i - 1];
		int n = len[i] - len[i - 1];
		memcpy(rev, str, n);
		rev[n] = 0;
		reverse(rev, rev + n);
		getexnext(rev, n); 
		exkmp(str, rev, n, n);
		ans += Match(str, n); //将每个字符串进行匹配
	}
	cout << ans << endl;

	return 0;
}

猜你喜欢

转载自blog.csdn.net/CaprYang/article/details/83421612
今日推荐