2084: [Poi2010]Antisymmetry

2084: [Poi2010]Antisymmetry

Time Limit: 10 Sec   Memory Limit: 259 MB
Submit: 577   Solved: 368
[ Submit][ Status][ Discuss]

Description

对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。

Input

第一行一个正整数N (N <= 500,000)。第二行一个长度为N的01字符串。

Output


一个正整数,表示反对称子串的个数。

Sample Input

8
11001011

Sample Output

7

hint
7个反对称子串分别是:01(出现两次), 10(出现两次), 0101, 1100和001011

HINT

Source

[ Submit][ Status][ Discuss]

对于任意一个反对称子串,在其两边分别添加0和1,新串必定还是反对称子串
找到规律,,剩下的就是计数了。
需要统计给定串中反对称子串的数量,反对称子串的扩大方式是在两边添加不同的数码
这和回文树的构造方式十分相似,构造一棵只含有偶数长度节点的回文树就解决了。
当然,要把回文树的模板稍作修改
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int maxn = 5E5 + 50;
typedef long long LL;

int n,cnt,p,ch[maxn][2],du[maxn],fail[maxn],g[maxn],len[maxn];
char s[maxn];
LL Ans;

queue <int> Q;

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n; scanf("%s",s + 1);
	for (int i = 1; i <= n; i++) s[i] -= '0';
	for (int i = 2; i <= n; i++)
	{
		if (len[p] == i - 1) p = fail[p];
		while (s[i-len[p]-1] == s[i]) 
		{
			if (!p) break;
			p = fail[p];
		}
		if (!p)
		{
			if (s[i] == s[i-1]) continue;
			if (!ch[p][s[i]]) ch[p][s[i]] = ++cnt;
			p = ch[p][s[i]]; ++g[p]; len[p] = 2;
			continue;
		}
		if (!ch[p][s[i]])
		{
			int tmp = p; bool flag = 1;
			ch[p][s[i]] = ++cnt;
			len[cnt] = len[p] + 2; p = fail[p];
			while (s[i-len[p]-1] == s[i]) 
			{
				if (!p) {flag = 0; break;}
				p = fail[p];
			}
			if (flag)
			{
				p = ch[p][s[i]];
				fail[cnt] = p; ++du[p];
			}
			p = tmp;
		}
		p = ch[p][s[i]]; ++g[p];
	}
	for (int i = 1; i <= cnt; i++)
		if (!du[i]) Q.push(i);
	while (!Q.empty())
	{
		int k = Q.front(); Q.pop();
		g[fail[k]] += g[k]; --du[fail[k]];
		if (!fail[k] || du[fail[k]]) continue;
		Q.push(fail[k]);
	}
	for (int i = 1; i <= cnt; i++) Ans += 1LL*g[i];
	cout << Ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值