UVALive_6284_Hyperdrome(可重排回文子串数+hash)

题型:字符串


题意:长度为n的字符串。求可重排回文子串的数目。


分析:

    n范围10^5

    最暴力的方法需要O(n^3),枚举起点终点,验证是否是回文串。复杂度多高,不可接受。

    优化一点,由于子串可以重排,所以只需要根据子串长度的奇偶性和字母的奇偶统计即可验证回文串,这一维优化为O(1),总复杂度O(n^2),仍然不可接受。

    所以就需要对O(n^2)下手了。

    由于只有大小写52个字母,用二进制表示当前位置时各字母的出现次数的奇偶性。

    例如串“abacb

    初始   000

         a   100

         b   110

         a   010

         c   011

         b   001

    这样的状态表示满足异或的性质,若当前状态在之前出现过,那么之前出现该状态位置到当前状态位置长度的字符串是可重排成为回文的。

    对于奇数长度的字符串,可以让52位中的某一位反转,再看之前有木有出现过相同状态。

    用long long表示状态,用map<LL,int>存每一个状态出现的次数。

    如此以来,复杂度为O(n*52*log(n))。

    但是还是会TLE,O(52*n)实在是优化不了了,只好采用hash把map的logn复杂度优化掉。


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>

#define LL long long
#define mt(a,b) memset(a,b,sizeof(a))
using namespace std;

const int MOD = 1000007;
const int M = 300100;
int n;
char str[M];

/**hash*/
LL val[MOD+10];
int next[MOD*2];
int tote;
int cnt[MOD*2];
int first[MOD+10];

/**add a data*/
void add(LL a){
	int sit = a % MOD;
	for(int i = first[sit];~i;i=next[i]){
		if(val[i] == a){
			cnt[i] ++;
			return;
		}
	}
	val[tote] = a;
	cnt[tote] ++;
	next[tote] = first[sit];
	first[sit] = tote ++;
}

/**query a's num*/
int siz(LL a){
	int sit = a % MOD;
	for(int i=first[sit];~i;i=next[i]){
		if(val[i] == a){
			return cnt[i];
		}
	}
	return 0;
}

int main(){
	while(~scanf("%d",&n)){
		scanf("%s",str);

		mt(cnt,0);
		mt(first,-1);
		tote = 0;
		add(0);

		LL ans = 0;
		LL pre = 0;
		for(int i=0;i<n;i++){
			int id;
			if(str[i]>='a' && str[i]<='z') id = str[i] - 'a';
			else id = str[i] - 'A' + 26;
			pre ^= (1LL<<id);
			ans += siz(pre);///偶数长度子串
			///奇数长度子串
			for(int j=0;j<52;j++){
				LL tmp = pre ^ (1LL<<j);
				ans += siz(tmp);
			}
			add(pre);
		}
		printf("%lld\n",ans);
	}
	return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值