Newcoder 39 F.重排的回文串(莫队算法+位运算)

本文介绍了一种高效算法,用于解决给定字符串上区间内能重排为回文串的子区间数量查询问题。利用前缀状态和莫队算法,通过离散化和预处理技术,实现了O(26n√n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给一个长为 nnn 的只含小写字母的字符串

每次查询一个区间$ [l,r]$ 内,有多少子区间可以重排为一个回文串

一个区间可以重排为一个回文串:

就是说我们可以以一定顺序排列这个区间内的所有数使得排列后为一个回文串

Input

第一行两个正整数n,mn,mn,m

第二行一个长为 nnn 的字符串

之后 mmm 行每行两个数$ l$ 和$ r$

(n,m≤6⋅104)(n,m\le 6\cdot 10^4)(n,m6104)

Output

对于每个询问,输出一个整数表示答案

Sample Input

6 5
zzqzzq
2 4
3 4
2 3
4 5
1 1

Sample Output

4
2
2
3
1

Solution

只需考虑区间中每个字母的奇偶性,显然只能至多一种字母出现奇数次,由于字符串只由小写字母组成,用262626010101来表示每种字母的奇偶性,假设SiS_iSi表示长度为iii的前缀的字母状态,那么区间[l,r][l,r][l,r]合法当且仅当Sr=Sl−1S_r=S_{l-1}Sr=Sl1Sl−1⊕SrS_{l-1}\oplus S_rSl1Sr222的整数次幂,其中⊕\oplus表示异或运算,故可以用莫队维护区间中每种状态SiS_iSi的出现次数,每次可以O(26)O(26)O(26)的更新答案和O(1)O(1)O(1)的维护,为此需要对SiS_iSi离散化编号,以及对每个rrr预处理可能合法的l−1l-1l1,总时间复杂度O(26nn)O(26n\sqrt{n})O(26nn)

Code

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=60005;
struct node
{
    int l,r,id;
}q[maxn];
int n,m,h[maxn],c[maxn],d[maxn],pos[maxn],num[maxn];
char s[maxn];
vector<int>f[maxn];
ll res,ans[maxn];
int cmp(node x,node y)
{
    if(pos[x.l]!=pos[y.l])return x.l<y.l;
    return x.r<y.r;
}
void update(int x,int v)//表示对第x个元素做删除(v=-1)或者添加(v=1) 
{
    if(v==1)
    {
    	res+=num[d[x]];
    	for(int i=0;i<f[x].size();i++)res+=num[f[x][i]];
    	num[d[x]]++;
    }
    else
    {
    	num[d[x]]--;
    	res-=num[d[x]];
    	for(int i=0;i<f[x].size();i++)res-=num[f[x][i]];
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    int mm=(int)sqrt(n);
    pos[0]=0;
    for(int i=1;i<=n;i++)pos[i]=(i-1)/mm+1;
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
    {
    	s[i]-='a';
    	c[i]=c[i-1]^(1<<s[i]);
    	h[i]=c[i];
    }
    h[0]=0;
    sort(h,h+n+1);
    int nn=unique(h,h+n+1)-h;
    for(int i=1;i<=n;i++)d[i]=lower_bound(h,h+nn,c[i])-h;
    for(int i=0;i<=n;i++)
    	for(int j=0;j<26;j++)
    	{
    		int pos=lower_bound(h,h+nn,c[i]^(1<<j))-h;
    		if(pos<nn&&h[pos]==(c[i]^(1<<j)))f[i].push_back(pos);
    	}
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].l--;
        q[i].id=i;
    }
    sort(q,q+m,cmp);
    res=0;
    int l=0,r=-1;
    for(int i=0;i<m;i++)
    {
        while(r<q[i].r)update(r+1,1),r++;
        while(r>q[i].r)update(r,-1),r--;
        while(l<q[i].l)update(l,-1),l++;
        while(l>q[i].l)update(l-1,1),l--;
        ans[q[i].id]=res; 
    }
    for(int i=0;i<m;i++)printf("%lld\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值