Newcoder 39 F.重排的迴文串(莫隊演算法+位運算)
阿新 • • 發佈:2018-11-02
Description
給一個長為 的只含小寫字母的字串
每次查詢一個區間$ [l,r]$ 內,有多少子區間可以重排為一個迴文串
一個區間可以重排為一個迴文串:
就是說我們可以以一定順序排列這個區間內的所有數使得排列後為一個迴文串
Input
第一行兩個正整數
第二行一個長為 的字串
之後 行每行兩個數$ l$ 和$ r$
Output
對於每個詢問,輸出一個整數表示答案
Sample Input
6 5
zzqzzq
2 4
3 4
2 3
4 5
1 1
Sample Output
4
2
2
3
1
Solution
只需考慮區間中每個字母的奇偶性,顯然只能至多一種字母出現奇數次,由於字串只由小寫字母組成,用 位 來表示每種字母的奇偶性,假設 表示長度為 的字首的字母狀態,那麼區間 合法當且僅當 或 是 的整數次冪,其中 表示異或運算,故可以用莫隊維護區間中每種狀態 的出現次數,每次可以 的更新答案和 的維護,為此需要對 離散化編號,以及對每個 預處理可能合法的 ,總時間複雜度
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;
}