提供一种和其他题解完全不同的解法。
记 \(P_0\) 为题中给出的序列,\(P_1\) 为 \(P_0\) 取反的结果。
记 \(S_{l\sim r}\) 表示 \(S_lS_{l+1}\dots S_{r}\)。
方便起见,\(P\) 下标从 \(0\) 开始,其余的串都是从 \(1\) 开始。
这里用 \(P_{0,i}=\operatorname{popcount(i)}\mod 2\) 来理解这个序列。
首先考虑找到 \(P_0\) 子串的性质。
Lemma.1:若 \(T\) 为 \(P_0\) 的子串且 \(|T|>1\),则存在拆分点 \(i\in [1,|T|)\),使得 \(T_{i\sim 1}=P_{0/1,1\sim i} \land T_{i+1\sim |T|}=P_{0/1,1\sim |T|-i}\)。
简单地说,就是可以把 \(T\) 拆分成两个串 \(T_1,T_2\),使得 \(T_2\) 是 \(P_{0/1}\) 的前缀,\(\operatorname{rev}(T_1)\) 是 \(P_{0/1}\) 的前缀(\(\operatorname{rev}\) 是字符串翻转)。
证明的话,设 \(T\) 为 \(P_{0,l\sim r}\),那么找到 \((l,r]\) 中 \(\operatorname{ctz}\) 最大的 \(i\),那么 \(i-1\) 就是一个合法的拆分点。简单地说,就是找到进位最大的一次,后面的是 \(P_{P_{0,i}}\) 的前缀,前面的翻转是 \(P_{P_{0,i-1}}\) 的前缀。
Lemma.2:若 \(T\) 是 \(P_0\) 的子串且 \(|T|>1\),那么 \(T\) 的拆分点至多只有两个。
你可以考虑把长度 \(\le n\) 的串都找一遍拆分点并输出,发现这个性质。
证明的话考虑反证就行了,大家有兴趣可以自己证明。
同时,如果你打过拆分点的表,会发现如果 \(T\) 有两个拆分点 \(i,j\),那么 \(|i-j|=2^k\),这个很好理解,中间这一段正反都要是 \(P_{0/1}\) 的前缀。
有了这些性质,你会发现一个串 \(T\) 是 \(P_{0}\) 的子串的充要条件就是 \(|T|=1\) 或 \(T\) 存在拆分点。
\(|T|=1\) 的情况只需要特判 \(|S|=1,k=0\) 的情况即可,下面考虑 \(|T|>1\)。
那么答案就是在所有前缀为 \(S\) 且长度为 \(|S|+k\) 的 \(01\) 串中【拆分点的个数】-【拆分点个数为 \(2\) 的个数】。
计算拆分点的个数和
考虑 \(<|S|\) 的拆分点 \(i\),只需要判断 \(S_{i\sim 1},S_{i+1\sim |S|}\) 是否是 \(P_{0/1}\) 的前缀即可。
对于 \(\ge|S|\) 的拆分点 \(i\),后面一半可以任选 \(P_{0/1}\) 的前缀,那么只需算前面有几个满足就行了,也就是计算 \(S\) 在 \(P_{0,0\sim |S|+k-1}\) 中出现几次,这个问题等会说。
计算拆分点个数为 \(2\) 的数量
考虑枚举拆分点之间的距离 \(2^t\),那么这种情况下,要求 \(T\) 的两个拆分点 \(i,j\) 满足 \(i+2^t=j,i-1\in [1,2^t],|T|-j \in [1,2^t]\)。
- 若 \(i < |S|\),那么只需暴力枚举 \(i\),判断是否合法即可。
- 若 \(i\ge |S|\),类似地,等价于计算 \(S\) 在 \(P_{0,l-|S|+1\sim r}\) 中的出现次数。
剩下的问题,就是计算 \(S\) 在 \(P_{0,l\sim r}\) 中的出现次数了,这个问题见 QOJ P6842,这题还是加强的。
大概思路就是要建出 \(S\) 的失配树。
那么从 \(u\) 开始往后匹配 \(P_{0,0\sim 2^k-1}\) 等价于 \(u\) 往后匹配 \(P_{0,0\sim 2^{k-1}-1}\),然后再匹配 \(P_{1,0\sim 2^{k-1}-1}\),倍增计算每个点走过的次数即可。
总时间复杂度 \(\Theta(T|S|^2+T|S|\log k)\),可以做到 \(\Theta(T|S|\log k)\)。
题外话,上面的 Lemma.2 也说明了答案不会超过 \(4(|S|+k)\),所以似乎 \(\mod (10^9+9)\) 显得有点多余。
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define all(a) (a).begin(),(a).end()
#ifdef DEBUG
template<class T>
ostream& operator << (ostream &out,vector<T> a){
out<<'[';
for(T x:a)out<<x<<',';
return out<<']';
}
template<class T>
vector<T> ary(T *a,int l,int r){
return vector<T>{a+l,a+1+r};
}
template<class T>
void debug(T x){
cerr<<x<<endl;
}
template<class T,class...S>
void debug(T x,S...y){
cerr<<x<<' ',debug(y...);
}
#else
#define debug(...) void()
#endif
const int N=1e2+10,K=60,mod=1e9+9;
int T,n;
ll k;
char a[N],b[N];
int pre[N][2],suf[N][2];
int ch[N][2],fail[N];
void getfail(){
for(int i=1;i<=n;i++)b[n+1-i]=a[i];
b[n+1]=0;
for(int i=1;i<=n;i++)ch[i-1][b[i]&1]=i;
for(int i=1;i<=n;i++){
for(int c:{0,1}){
int &v=ch[i][c],x=ch[fail[i]][c];
if(v)fail[v]=x;
else v=x;
}
}
}
int to[K][N][2];
void init(){
for(int i=0;i<=n;i++){
for(int c:{0,1}){
to[0][i][c]=ch[i][c];
}
}
for(int i=1;i<K;i++){
for(int u=0;u<=n;u++){
for(int c:{0,1}){
int t=to[i-1][u][c];
to[i][u][c]=to[i-1][t][!c];
}
}
}
}
ll calc(ll l,ll r){
// int ans=0;
// for(int i=0,u=0;i<r;i++){
// u=ch[u][__builtin_parity(i)];
// ans+=i>=l-1&&u==n;
// }
// for(int i=0,u=0;i<r;i++){
// u=ch[u][!__builtin_parity(i)];
// ans+=i>=l-1&&u==n;
// }
// debug(l,r,b+1,ans);
// return ans;
ll las=l;
r++;
static ll cnt[K][N][2];
memset(cnt,0,sizeof cnt);
int u[2]={0,0};
for(int i=K-1,c=0;i>=0;i--){
if(l>>i&1){
for(int x:{0,1}){
u[x]=to[i][u[x]][c^x];
}
c^=1;
}
}
for(int i=0;i<K;i++){
if(l+(1ll<<i)<=r&&(l>>i&1)){
int c=__builtin_parityll(l);
for(int x:{0,1}){
cnt[i][u[x]][c^x]++,u[x]=to[i][u[x]][c^x];
}
l+=1ll<<i;
}
}
for(int i=K-1;i>=0;i--){
if(l+(1ll<<i)<=r&&(~l>>i&1)){
int c=__builtin_parityll(l);
for(int x:{0,1}){
cnt[i][u[x]][c^x]++,u[x]=to[i][u[x]][c^x];
}
l+=1ll<<i;
}
}
for(int i=K-1;i>=1;i--){
for(int u=0;u<=n;u++){
for(int c:{0,1}){
int t=to[i-1][u][c];
cnt[i-1][u][c]+=cnt[i][u][c];
cnt[i-1][t][!c]+=cnt[i][u][c];
}
}
}
// debug(las,r-1,b+1,cnt[0][n][0]+cnt[0][n][1]);
return cnt[0][n][0]+cnt[0][n][1];
}
void get(){
scanf("%s%lld",a+1,&k),n=strlen(a+1);
if(n==1&&!k)return puts("1"),void();
for(int i=1;i<=n;i++){
for(int c:{0,1}){
pre[i][c]=1;
for(int j=0;j<i;j++){
pre[i][c]&=__builtin_parity(j)^c==a[i-j]-'0';
}
suf[i][c]=1;
for(int j=0;j<=n-i;j++){
suf[i][c]&=__builtin_parity(j)^c==a[i+j]-'0';
}
}
}
getfail(),init();
ll ans=0;
for(int i=1;i<n;i++){
ans+=(pre[i][0]+pre[i][1])*(suf[i+1][0]+suf[i+1][1]);
// debug(i,pre[i][0]+pre[i][1],suf[i+1][0]+suf[i+1][1],ans);
}
ans+=calc(0,n+k-1)*2;
// debug(ans);
for(ll len=1;len+2<=n+k;len<<=1){
ll l=max(len+1,n+k-len),r=min(len+len,n+k-1ll);
if(l>r)continue;
// debug(n+k,len,l,r);
ans-=calc(l,r);
for(ll i=l;i<n;i++){
ans-=(pre[i][0]+pre[i][1])*(suf[i-len+1][0]+suf[i-len+1][1]);
}
}
printf("%lld\n",ans%mod);
}
void clr(){
for(int i=0;i<=n;i++){
fail[i]=ch[i][0]=ch[i][1]=0;
}
}
int main(){
freopen(".in","r",stdin);
// freopen(".out","w",stdout);
for(scanf("%d",&T);T--;clr())get();
return 0;
}
标签:cnt,前缀,zhengjun,--,题解,int,拆分,sim
From: https://www.cnblogs.com/A-zjzj/p/17978171