Loading

题解-洛谷P7114 字符串匹配

题面

洛谷P7114 字符串匹配

\(T\) 组测试数据。给定字符串 \(S\),问有多少不同的非空字符串 \(A\)\(B\)\(C\) 满足 \(S=ABABAB...ABC\)\(A\) 中出现奇数次的字符数不多于 \(C\)

数据范围:\(1\le T\le 5\)\(1\le |S|\le 2^{20}\)


这估计是我场上唯一做出来的题目了,NOIP2020 游记 也不放洛谷博客里了。


提供一个 \(\Theta(n)\) 的做法,下标从 \(0\) 开始。

求出 \(S\)Z 数组,\(ze_i\) 表示满足 \(s[0,ze_i)=s[i,i+ze_i)\) 的最大值。

根据 \(s[0,ze_i)=s[i,i+ze_i)\) 可以推出 \(s[i,ze_i)=s[2i,i+ze_i)\)

所以可以枚举 \(AB\) 的长度 \(i\),易得这个串在前缀重复了 \(\lfloor\frac{ze_i}{i}\rfloor+1\) 次。

然后解决关于出现奇数次的字符的限制。

维护一个树状数组,表示每个 \(j<i\) 的前缀出现奇数次字符数的集合。

计算前缀出现奇数次字符数可以 \(\Theta(1)\),值域为字符集的树状数组时间复杂度也是 \(\Theta(1)\) 的。

考虑对于当前的所有 \(ABAB...AB\) 对应的 \(C\) 中出现奇数次字符数:只有两种可能。

一种是当前后缀出现奇数次字符数,一种是全局出现奇数次字符数。先预处理后者,前者可以 \(\Theta(1)\) 维护。

因为 \(ABAB\) 中出现奇数次字符数肯定为 \(0\),所以对于 \(\lfloor\frac {\lfloor\frac{ze_i}{i}\rfloor+1}{2}\rfloor\) 的肯定是全局次数,\(\lceil\frac {\lfloor\frac{ze_i}{i}\rfloor+1}{2}\rceil\) 的肯定是后缀次数。

然后对于两种情况,答案加上树状数组查询前缀满足条件的 \(A\) 的数量乘以出现次数即可。

所以总时间复杂度 \(\Theta(n)\)


代码

回家手敲的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define x first
#define y second
#define bg begin()
#define ed end()
#define pb push_back
#define mp make_pair
#define sz(a) int((a).size())
#define R(i,n) for(int i(0);i<(n);++i)
#define L(i,n) for(int i((n)-1);i>=0;--i)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;

//Data
const int N=1<<20,C=26;
int n,all,last,now,tot[C],cnt[C];
string str;

//FenwickTree
int fen[C|1];
void add(int i,int v){for(;i<C+1;i|=i+1) fen[i]+=v;}
int sum(int i,int v=0){for(;i>=0;(i&=i+1)--) v+=fen[i]; return v;}

//Zebra
int ze[N];
void zebra(){
    int l=0;
    R(i,n) ze[i]=0;
    R(i,n)if(i){
        if(l+ze[l]>i) ze[i]=min(l+ze[l]-i,ze[i-l]);
        while(i+ze[i]<n&&str[ze[i]]==str[i+ze[i]]) ze[i]++;
        if(i+ze[i]>l+ze[l]) l=i;
    }
}

//Main
void Main(){
    cin>>str,n=sz(str),zebra();
    R(i,n)if(i+ze[i]==n) ze[i]--;
    ll ns=all=last=now=fen[C]=0;
    R(c,C) tot[c]=cnt[c]=fen[c]=0;
    R(i,n) tot[str[i]-'a']++;
    R(c,C) all+=(tot[c]&1); last=all;
    R(i,n){
        if(tot[str[i]-'a']&1) last--; else last++;
        if(cnt[str[i]-'a']&1) now--; else now++;
        tot[str[i]-'a']--,cnt[str[i]-'a']++;
        if(i&&i<n-1){
            int t=ze[i+1]/(i+1)+1;
            // cout<<"i="<<i<<" t="<<t<<'\n';
            ns+=1ll*(t/2)*sum(all)+1ll*(t-t/2)*sum(last);
        }
        // cout<<"now="<<now<<" last="<<last<<" all="<<all<<'\n';
        add(now,1);
    }
    cout<<ns<<'\n';
}
int main(){
    //freopen("string.in","r",stdin);
    //freopen("string.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T; cin>>T;
    while(T--) Main();
    return 0;
}

祝大家学习愉快!

posted @ 2020-12-06 09:57  George1123  阅读(1028)  评论(0编辑  收藏  举报