【ybt金牌导航2-1-5】【luogu P4199】对称子序列 / 万径人踪灭

对称子序列 / 万径人踪灭

题目链接:ybt金牌导航2-1-5 / luogu P4199

题目大意

给你一个字符串,问你有多少个子串,满足它们位置和字符都对于一条对称轴对称,且在原字符串中不是连续的一段。

思路

首先我们看到关于一条轴对称,我们可以先直接搞个 Manacher。

但是你会发现题目有一个限制条件就是限制你回文串都不可以用,但它是满足第一个条件的。
然后你会发现这个限制条件就是专门只针对这种情况的。

那你就考虑容斥,求出满足第一个条件的,在减去回文串个数(就是限制条件不准的)。

那接着问题就是怎么求满足第一个条件的了,我们来看看它是什么:

位置和字符都关于某条对称轴对称。

那你会想到,它的意思就是要对于每条对称轴 i i i,找有多少对 a,多少对 b,它们两个的位置的平均值是 i i i
那暴力找肯定会超时,我们考虑优化。
首先看到平均值要除二,我们就先乘二,那就相当于找它们位置相加等于 2 i 2i 2i

位置相加,我们其实有一种奇妙的方式,那就是把字符串看成一个多项式。
以找 a 的对为例,我们就把 a 看做 1 1 1,b 看做 0 0 0,这样,你就得到了一个序列,我们把它作为一个多项式,求它自己和它自己的卷积。

你可以发现卷积得到多项式的意义就是第 i i i 位数表示有多少个 a 对位置相加等于 i i i

那你求 a 做一遍,求 b 做一遍,再处理一下奇偶的问题。
(可能中间是字符,那那个字符自己乘自己是可以到这,但不能选,所以要特殊处理)
然后由于 a + b = c , b + a = c a+b=c,b+a=c a+b=c,b+a=c,这两种情况相当于重复了,我们要除 2 2 2

然后你考虑怎么通过这个求出满足第一个条件的个数。
那你可以想到,对于每条对称轴,每一对点要么都选,要么都不要,而除了一对都不要,你怎么要都是可以的。
那如果对数是 x x x,那这一条对称轴贡献的个数就是 2 x − 1 2^x-1 2x1。(减一就是不能一个都不要)

然后把每个对称轴的个数相加,再减去回文串的个数就好了。
(回文串个数就是最长回文串半径,由于做的时候求的最长回文串半径是处理后的字符串,所以真正的还要除二)

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
#define mo 1000000007

using namespace std;

struct complex {
	double x, y;
	complex (double xx = 0, double yy = 0) {
		x = xx;
		y = yy;
	}
}a[5000001];
char c[500001], s[500001];
ll nn, n, dis[500001], num[5000001];
ll limit, l_size, an[5000001];
double Pi = acos(-1.0);
ll ans, two[500001];

complex operator +(complex a, complex b) {
	return complex(a.x + b.x, a.y + b.y);
}

complex operator -(complex a, complex b) {
	return complex(a.x - b.x, a.y - b.y);
}

complex operator *(complex a, complex b) {
	return complex(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}

void Manacher() {
	ll r = -1, mid = -1;
	for (ll i = 1; i <= n; i++) {
		if (i > r) dis[i] = 1;
			else dis[i] = min(dis[2 * mid - i], r - i);
		while (i - dis[i] >= 1 && i + dis[i] <= n && s[i + dis[i]] == s[i - dis[i]])
			dis[i]++;
		if (i + dis[i] > r) {
			r = i + dis[i] - 1;
			mid = i;
		}
	}
}

void FFT(complex *now, ll op) {
	for (ll i = 0; i < limit; i++)
		if (i > an[i]) swap(now[i], now[an[i]]);
	
	for (ll mid = 1; mid < limit; mid <<= 1) {
		complex Wn(cos(Pi / mid), op * sin(Pi / mid));
		for (ll R = mid << 1, j = 0; j < limit; j += R) {
			complex w(1, 0);
			for (ll k = 0; k < mid; k++, w = w * Wn) {
				complex x = now[j + k], y = w * now[j + mid + k];
				now[j + k] = x + y;
				now[j + mid + k] = x - y;
			}
		}
	}
}

int main() {
	scanf("%s", c + 1);
	nn = strlen(c + 1);
	
	n = nn * 2 + 1;
	for (ll i = 1; i <= n; i++)
		if (i & 1) s[i] = '#';
			else s[i] = c[i >> 1];
	
	Manacher();//处理出回文串
	
	limit = 1;//FFT预处理
	while (limit <= 2 * nn) {
		l_size++;
		limit <<= 1;
	}
	
	for (ll i = 0; i < limit; i++)
		an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1));
	
	for (ll i = 1; i <= nn; i++)//构a为1,b为0的
		if (c[i] == 'a') a[i].x = 1.0;
	
	FFT(a, 1);
	for (ll i = 0; i <= limit; i++)//自己乘自己
		a[i] = a[i] * a[i];
	FFT(a, -1);
	
	for (ll i = 1; i <= (nn << 1) + 1; i++) {
		num[i] += (ll)(a[i].x / limit + 0.5);
	}
	
	memset(a, 0, sizeof(a));
	
	for (ll i = 1; i <= nn; i++)//构a为0,b为1的
		if (c[i] == 'b') a[i].x = 1.0;
	
	FFT(a, 1);
	for (ll i = 0; i <= limit; i++)
		a[i] = a[i] * a[i];
	FFT(a, -1);
	
	for (ll i = 1; i <= (nn << 1) + 1; i++) {
		num[i] += (int)(a[i].x / limit + 0.5);
	}
	
	for (ll i = 1; i <= (nn << 1) + 1; i++)//处理
		num[i] = ((num[i] - ((i & 1) ^ 1)) >> 1) + ((i & 1) ^ 1);
	
	two[0] = 1;
	for (ll i = 1; i <= limit; i++)
		two[i] = (two[i - 1] * 2) % mo;
	for (ll i = 1; i <= (nn << 1) + 1; i++)//算答案
		ans = (ans + ((two[num[i]] - 1 - (dis[i] >> 1)) % mo + mo) % mo) % mo;
	
	printf("%lld", ans);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值