给出只包含小写字母 a a a, b b b的两个字符串 s s s, t t t, q q q次询问,每次询问 s [ l . . . r ] s[l...r] s[l...r]和 t t t的最长公共子串长度。
建出 t t t的后缀自动机后在自动机上跑 s s s串得到对于每个 s s s的前缀在 t t t中匹配的最长长度 p i p_i pi,然后对于 s [ l . . . r ] s[l...r] s[l...r],二分答案 m i d mid mid,检查是否有 max i = l + m i d − 1 r p i ≥ m i d \max_{i=l+mid-1}^rp_i \geq mid maxi=l+mid−1rpi≥mid即可。
A C C o d e \mathcal AC \ Code AC Code
#include
#define maxn 400005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lim 18
using namespace std;
char s[maxn],t[maxn];
int q,ls,lt;
int last , fa[maxn] , len[maxn] , tr[maxn][2] , tot;
int st[lim][maxn] , lg[maxn];
void ins(int c){
int u = ++tot , p = last , q;
len[last = u] = len[p] + 1;
for(;p != -1 && !tr[p][c];p=fa[p]) tr[p][c] = u;
if(p == -1) fa[u] = 0;
else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
else{
int v = ++tot;
memcpy(tr[v],tr[q],sizeof tr[q]),fa[v] = fa[q] , len[v] = len[p] + 1;
for(;p != -1 && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[q] = fa[u] = v;
}
}
int qry(int u,int v){
int t = lg[v-u+1];
return max(st[t][u] , st[t][v-(1<<t)+1]);
}
int main(){
scanf("%s%s%d",s,t,&q);
fa[0] = -1;
ls = strlen(s) , lt = strlen(t);
rep(i,0,lt-1) ins(t[i] - 'a');
int u=0,L=0;
rep(i,0,ls-1){
int c = s[i] - 'a';
for(;u!=-1 && tr[u][c]==0;u=fa[u]);
if(u == -1) u = 0 , L = 0;
else L = min(L+1 , len[u] + 1) ,u = tr[u][c];
st[0][i] = L;
}
rep(i,2,ls) lg[i] = lg[i >> 1] + 1;
rep(j,1,lim-1) rep(i,0,ls-(1<<j))
st[j][i] = max(st[j-1][i] , st[j-1][i+(1<<j-1)]);
for(;q--;){
int l,r;scanf("%d%d",&l,&r);l--,r--;
int L = 0 , R = min(r-l+1,lt) , mid;
for(;L<R;){
mid = L+R+1 >> 1;
if(qry(l+mid-1,r) < mid) R = mid - 1;
else L = mid;
}
printf("%d\n",L);
}
}
三倍经验
具体来说就是利用异或的性质,所有数异或起来等于出现奇数次的数的异或和。
如果我们要得到单独一个数字,就 h a s h hash hash一下,具体来说我们只统计 m o d p = i \bmod p = i modp=i的数的异或和。
那么多用几个 p p p,统计出来出现次数多的数就很有可能是单独一个数字的异或和,反之多个数字的异或和很难在多个不同的 p p p下都一起满足条件。
A C C o d e \mathcal AC \ Code AC Code
#include
#define vi vector
using namespace std;
int chk(int x){
for(int i=2;i*i<=x;i++) if(x % i == 0) return 0;
return 1;
}
int n,K;
vi mod;
int a[20][20100];
map<int,int>mAp;
int main(){
scanf("%d%d",&n,&K);
for(int i=10000;mod.size() < 20;i++) if(chk(i) && rand() % 2)
mod.push_back(i);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
for(int j=0;j<mod.size();j++)
a[j][x % mod[j]] ^= x;
}
for(int j=0;j<mod.size();j++) for(int i=0;i<mod[j];i++)
if(a[j][i]) mAp[a[j][i]]++;
vector<int>ans;
for(auto v:mAp) if(v.second > 2) ans.push_back(v.first);
sort(ans.begin(),ans.end());
for(int i=0;i<ans.size();i++) printf("%d\n",ans[i]);
}
给出 S S S, Q Q Q次询问 S S S中最短的子串 t t t的长度使得字符串 m m m在 t t t中出现了 k k k次。
所有询问m互不相同。
可以想到求出后缀自动机的 r i g h t right right集合 S S S后 O ( ∣ S ∣ ) O(|S|) O(∣S∣)回答一次询问。
然后就这样写,然后就可以过。
因为所有询问 m m m互不相同,又因为对于所有长度为 k k k的串他们的 r i g h t right right集合大小之和为 O ( n ) O(n) O(n)。
串不相同代表着只有 O ( n ) O(\sqrt n) O(n)种不同的长度,所以所有询问的复杂度为 O ( n n ) O(n\sqrt n) O(nn)
事实上因为我们只需要 m m m的 r i g h t right right集合,所以可以不用写后缀树上启发式合并,直接离线求出所有 m m m的 A C AC AC自动机,拿 s s s在自动机上跑,对于 s s s的前缀所在的节点我们需要更新所有它的后缀的 m m m的 r i g h t right right集合,新开一个数组 f a fa fa表示沿着 f a i l fail fail链跑下一个是 m m m中的一个的节点,因为 r i g h t right right集合大小是 O ( n n ) O(n \sqrt n) O(nn),每次暴力爬 f a fa fa也是 O ( n n ) O(n \sqrt n) O(nn)的。
来了来了,对于这种屑题,肯定是要写 b i t s e t bitset bitset的啦。
A C C o d e \mathcal AC \ Code AC Code
#include
#define maxn 100005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
using namespace std;
char s[maxn],m[maxn];
int n,q,K;
bitset<maxn>C[26];
int main(){
scanf("%s",s);
n = strlen(s);
rep(i,0,n-1) C[s[i] - 'a'][i] = 1;
scanf("%d",&q);
for(;q--;){
scanf("%d%s",&K,m);
int L = strlen(m);
static bitset<maxn>ans;
ans.set();
rep(i,0,L-1) ans &= C[m[i] - 'a'] >> i;
vector<int>a;
for(int i=ans._Find_first();i!=ans.size();i=ans._Find_next(i))
a.push_back(i);
if(a.size() < K) puts("-1");
else{
int ret = 0x3f3f3f3f;
rep(i,K-1,a.size()-1) ret = min(ret , a[i] - a[i-K+1] + L);
printf("%d\n",ret);
}
}
}
分类讨论屑题。
g r a y gray gray串的长度只有可能是 2 k − 1 2^k-1 2k−1,所以可以计算出所有 g r a y gray gray串。
改字符,
一.变为 g r a y gray gray的情况:
1.改中间字符原来在外面出现过现在没出现过,暴力哈希判断即可。
2.改两边字符,原来差一个字符匹配现在不差了,对于所有 g r a y gray gray串求出往左/右差一个字符的位置贡献上去。
二.变成不是 g r a y gray gray的情况:
1.改中间字符原来在外面没出现过现在出现过,暴力哈希判断即可。
2.改两边字符,原来就是 g r a y gray gray串,只要改了两边就一定不是,对于所有 g r a y gray gray串写区间加即可。
综上,每次就是先减去改字符是中间字符的贡献,再减去(二.2),然后枚举改成那个字符,加上(一.2),加上中间字符的贡献。
真的屑
A C C o d e \mathcal AC \ Code AC Code
#include
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
#define lim 17
#define LL long long
#define mod 998244353
#define S 131
char s[maxn];
int f[lim][maxn],n;
int sm[26][maxn];
LL hs[maxn],pw[maxn],p12[maxn][26],p22[maxn],Sm;
LL calc(int u,int v){ return ((hs[v] - hs[u-1] * pw[v-u+1]) % mod + mod) % mod; }
int LCP(int u,int v){
int L = 0 , R = min(n-u+1,n-v+1) , mid;
while(L<R){
mid = (L+R+1) >> 1;
if(calc(u,u+mid-1) == calc(v,v+mid-1))
L = mid;
else
R = mid - 1;
}
return L;
}
int main(){
scanf("%s",s+1);
n = strlen(s+1);
pw[0] = 1;
rep(i,1,n){
f[0][i] = 1;
Sm++;
hs[i] = (hs[i-1] * S + s[i]) % mod;
pw[i] = pw[i-1] * S % mod;
rep(j,0,25)
sm[j][i] = sm[j][i-1] + (s[i] - 'a' == j);
}
rep(j,1,lim-1) rep(i,1,n-(1<<j+1)+2){
if(f[j-1][i] && f[j-1][i+(1<<j)] && sm[s[i+(1<<j)-1] - 'a'][i+(1<<j+1)-2] - sm[s[i+(1<<j)-1] - 'a'][i-1] == 1
&& calc(i,i+(1<<j)-2) == calc(i+(1<<j),i+(1<<j+1)-2))
f[j][i] = 1 , Sm += ((1<<j+1)-1ll) * ((1<<j+1)-1ll) , p22[i] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll) ,
p22[i+(1<<j)-1] -= ((1<<j+1)-1ll) * ((1<<j+1)-1ll) , p22[i+(1<<j)] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll),
p22[i+(1<<j+1)-1] -= ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
else if(f[j-1][i]){
int t = min(LCP(i,i+(1<<j)) , (1<<j)-1);
if(calc(i,i+t-1) == calc(i+(1<<j),i+(1<<j)+t-1)
&& calc(i+t+1,i+(1<<j)-2) == calc(i+(1<<j)+t+1,i+(1<<j+1)-2)
&& sm[s[i+(1<<j)-1] - 'a'][i+(1<<j)-1] - sm[s[i+(1<<j)-1] - 'a'][i-1] == 1)
p12[i+(1<<j)+t][s[i+t]-'a'] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
if(f[j-1][i+(1<<j)] && calc(i,i+t-1) == calc(i+(1<<j),i+(1<<j)+t-1)
&& calc(i+t+1,i+(1<<j)-2) == calc(i+(1<<j)+t+1,i+(1<<j+1)-2)
&& sm[s[i+(1<<j)-1] - 'a'][i+(1<<j+1)-2] - sm[s[i+(1<<j)-1] - 'a'][i+(1<<j)-2] == 1)
p12[i+t][s[i+t+(1<<j)]-'a'] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
}
else if(f[j-1][i+(1<<j)]){
int t = min(LCP(i,i+(1<<j)) , (1<<j)-1);
if(calc(i,i+t-1) == calc(i+(1<<j),i+(1<<j)+t-1)
&& calc(i+t+1,i+(1<<j)-2) == calc(i+(1<<j)+t+1,i+(1<<j+1)-2)
&& sm[s[i+(1<<j)-1] - 'a'][i+(1<<j+1)-2] - sm[s[i+(1<<j)-1] - 'a'][i+(1<<j)-2] == 1)
p12[i+t][s[i+t+(1<<j)]-'a'] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
}
}
rep(i,1,n) p22[i] += p22[i-1];
LL ans = Sm;
rep(i,1,n){
LL t = Sm;
rep(j,1,lim-1) if(i-(1<<j)+1 > 0)
if(f[j][i-(1<<j)+1])
t -= ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
t -= p22[i];
rep(j,0,25) if(j != s[i] - 'a'){
LL ret = t + p12[i][j];
rep(p,1,lim-1) if(i-(1<<p)+1 > 0){
if(f[p-1][i-(1<<p)+1] && f[p-1][i+1] && calc(i-(1<<p)+1,i-1) == calc(i+1,i+(1<<p)-1) &&
sm[j][i-1] - sm[j][i-(1<<p)] == 0)
ret +=((1<<p+1)-1ll) * ((1<<p+1)-1ll);
}
ans = max(ans , ret);
}
}
printf("%lld\n",ans);
}
#include
#define maxn 200005
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define inf 0x3f3f3f3f
using namespace std;
int XOR,qXOR,n,tot,ch[maxn*30][2],last,a[maxn];
struct node{
int a[30],sz;
void ins(int u){ ++sz;rep(i,0,29) if(u>>i&1) a[i]++; }
LL sum(int x=inf){
LL r = 0;
rep(i,0,29) r += (1ll << i) * min(x , XOR>>i&1 ? sz - a[i] : a[i]);
return r;
}
}s[maxn],tr[maxn*30];
void ins(int x){
for(int u=1,p=29;p>=0;tr[u=ch[u][x>>p&1]].ins(x),p--)
if(!ch[u][x>>p&1]) ch[u][x>>p&1]=++tot;
}
LL sum(int x){
if(x > last) return s[x].sum();
int u=1;LL r = 0;
per(i,29,0){
int c=(qXOR>>i&1);
if(x <= tr[ch[u][c]].sz) u =ch[u][c];
else r += tr[ch[u][c]].sum(),x-=tr[ch[u][c]].sz,u=ch[u][!c];
}
return r + tr[u].sum(x);
}
int main(){
scanf("%d",&n);int x;
rep(i,1,n) scanf("%d",&x),a[i]=x,s[i] = s[i-1] , s[i].ins(x);
int Q;scanf("%d",&Q);
tot = 1;
for(int op,l,r;Q--;){
scanf("%d",&op);
if(op == 1) scanf("%d",&x),x^=XOR,s[n+1]=s[n],s[++n].ins(x),a[n]=x;
if(op == 2) scanf("%d%d",&l,&r),printf("%lld\n",sum(r)-sum(l-1));
if(op == 3) scanf("%d",&x),XOR ^= x;
if(op == 4) for(qXOR = XOR;last < n;) ins(a[++last]);
}
}
社论
设字符串 s s s的最短周期为 L L L, L L L的计算可以简单通过 k m p kmp kmp得到。
那么对 L L L分类讨论,
L = 1 L=1 L=1则应该对于一个全为 0 0 0的字符串。
L = ∣ s ∣ L=|s| L=∣s∣则应该对应一个前 L − 1 L-1 L−1位全为 0 0 0,最后一位为 1 1 1的字符串。
2 L ≤ ∣ s ∣ 2L \leq |s| 2L≤∣s∣,则将字符串分解为 t t t t . . . t ′ tttt...t' tttt...t′的形式,递归求 t t ′ tt' tt′的答案,然后根据最短周期倒推出 s s s所对应的字符串,之所以可以这样做,是因为可以通过有两个周期 p , q p,q p,q并且 p + q ≤ ∣ s ∣ p+q \leq |s| p+q≤∣s∣那么他们的 gcd \gcd gcd也是周期来证明,最短周期是 l l l的情况下长度大于 l + ( ∣ s ∣ m o d l ) l+(|s|\bmod l) l+(∣s∣modl)的所有周期其实都是 l l l的倍数。
因为我们递归求出的答案是一定会满足所有 ≤ l + ( ∣ s ∣ m o d l ) \leq l+(|s|\bmod l) ≤l+(∣s∣modl)的周期,所以 t t t t . . t ′ tttt..t' tttt..t′是可以满足题意的。
2 L > ∣ s ∣ 2L\gt |s| 2L>∣s∣,将字符串分解为 t a t tat tat的形式,其中 ∣ t ∣ = ∣ s ∣ m o d L |t| = |s| \bmod L ∣t∣=∣s∣modL,递归求 t t t的答案,发现 a a a要么是全为 0 0 0,要么是只有最后一个为 1 1 1,简单 k m p kmp kmp判断一下即可。
A C C o d e \mathcal AC \ Code AC Code
#include
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
char s[maxn];
int n,nxt[maxn];
void solve(char *s,int n){
for(int j=-1,k=0;k<n;)
if(j == -1 || s[j] == s[k]) nxt[++k] = ++j;
else j = nxt[j];
if(nxt[n] == n-1){
rep(i,0,n-1)s[i] = '0';
return;
}
if(nxt[n] == 0){
rep(i,0,n-2) s[i] = '0';
s[n-1] = '1';
return;
}
int L = n - nxt[n];
if(2 * L <= n){
solve(s+(n/L-1)*L,n%L+L);
per(i,n-L-1,0) s[i] = s[i+L];
}
else{
int l = n % L;
solve(s,l);
rep(i,1,l) s[n-i] = s[l-i];
rep(i,l,n-l-1) s[i] = '0';
for(int j=-1,k=0;k<=n;)
if(j == -1 || s[j] == s[k]) nxt[++k] = ++j;
else j = nxt[j];
if(n - nxt[n] != L) s[n-l-1] = '1';
}
}
int main(){
int T;
scanf("%d",&T);
nxt[0] = -1;
for(;T--;){
scanf("%s",s+1);
n = strlen(s+1);
solve(s+1,n);
puts(s+1);
}
}
如果从前往后第一次失配和从后往前第一次失配的位置之差 ≤ K − 1 \leq K-1 ≤K−1则匹配,求 p i p_i pi在 s s s中出现次数,那么我们建出 p i p_i pi的 A C AC AC自动机,对于 p i p_i pi的前 x x x个字符,在 A C AC AC自动机上有个位置 a a a,对于 p i [ x + K + 1.... ∣ p i ∣ ] p_i[x+K+1....|p_i|] pi[x+K+1....∣pi∣],我们求出 p i p_i pi的反串的 A C AC AC自动机,那么这个 p i [ x + K + 1.... ∣ p i ∣ ] p_i[x+K+1....|p_i|] pi[x+K+1....∣pi∣]反过来进入自动机也有一个位置 b b b。
对于 s s s的前 y y y个字符在正串自动机上有个位置 c c c,后 ∣ s ∣ − y − K |s|-y-K ∣s∣−y−K个字符在反串自动机上也有个位置 d d d。
s s s和 p i p_i pi同时删去中间的 K K K个字符后 p i p_i pi在 s s s中出现,可以看做在 A C AC AC自动机的后缀树上 c c c在 a a a的子树中, d d d在 b b b的子树中。
但是可能会有多种删去连续 K K K个字符的方案使得他们相等,会算重,我们发现这些删去的 K K K个字符的所有方案是连续的(比如删去区间是 [ x , x + K + 1 ] , [ x + 1 , x + K + 2 ] , [ x + 2 , x + K + 3 ] [x,x+K+1],[x+1,x+K+2],[x+2,x+K+3] [x,x+K+1],[x+1,x+K+2],[x+2,x+K+3]),所以相邻两个方案( [ x , x + K + 1 ] , [ x + 1 , x + K + 2 ] [x,x+K+1],[x+1,x+K+2] [x,x+K+1],[x+1,x+K+2])对应了一个删去 K − 1 K-1 K−1个字符的方案( x + 1 , x + K + 1 x+1,x+K+1 x+1,x+K+1),所以直接减去删去 K − 1 K-1 K−1个字符的方案即可,但是因为 [ 0.... K − 1 ] [0....K-1] [0....K−1]不能对应相邻两个方案,所以不能计算形如这类的贡献。
那么就是两棵树,求在两棵树中点都在当前点对的点的子树内的数量,这可以看做二维平面上的数点问题,但是还有更简单的做法,用天天爱跑步的方法树上差分后即可用 d f s dfs dfs解决一棵树的限制。
A C C o d e \mathcal AC \ Code AC Code
#include
#define maxn 400005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define pb push_back
#define maxc 94
using namespace std;
int K,n,m;
struct node{int id,x,y;node(const int &id=0,const int &x=0,const int &y=0):id(id),x(x),y(y){}};
vector<node>v1[maxn];
vector<int>v2[maxn];
char s[maxn],t[maxn];
int tr[maxn][maxc] , fa[maxn] , tot , pos[maxn] , ps[maxn];
int st[maxn],ed[maxn],tim,ans[maxn];
vector<int>G[maxn];
void dfs(int u){
st[u] = ++tim;
for(int v:G[u]) dfs(v);
ed[u] = tim;
}
int t1[maxn],t2[maxn];
void upd(int u,int v,int *tr){ for(;u<=tot+1;u+=u&-u) tr[u] += v; }
int qry(int u,int *tr){ int r=0;for(;u>0;u-=u&-u) r += tr[u]; return r; }
void dfs2(int u){
for(node t:v1[u]) ans[t.id] -= qry(ed[t.x],t1) - qry(st[t.x]-1,t1) - qry(ed[t.y],t2) + qry(st[t.y]-1,t2);
for(int t:v2[u]) upd(st[pos[t+K+1]],1,t1),upd(st[pos[t+K]],1,t2);
for(int v:G[u]) dfs2(v);
for(node t:v1[u]) ans[t.id] += qry(ed[t.x],t1) - qry(st[t.x]-1,t1) - qry(ed[t.y],t2) + qry(st[t.y]-1,t2);
}
int main(){
scanf("%d%s%d",&K,s+1,&n);
m = strlen(s+1);
rep(i,1,n){
scanf("%s",t+1);
int L = strlen(t+1);
if(L <= K){
ans[i] = m-L+1;
continue;
}
int u = 0;
rep(i,1,L){
int v = t[i]-33;
if(!tr[u][v]) tr[u][v] = ++tot;
u = tr[u][v];
}
ps[L+1] = u = 0;
per(i,L,1){
int v = t[i] - 33;
if(!tr[u][v]) tr[u][v] = ++tot;
u = tr[u][v];
ps[i] = u;
}
for(int j=0,u=0;j+K<=L;u=tr[u][t[j+1]-33],j++)
v1[u].pb(node(i,ps[j+K+1],j?ps[j+K]:maxn-1));
}
static int q[maxn],L=0,R=0;
rep(i,0,93) if(tr[0][i]) q[R++] = tr[0][i];
for(int u;L<R;){
u = q[L++];
rep(i,0,93) if(tr[u][i]) fa[tr[u][i]] = tr[fa[u]][i] , q[R++] = tr[u][i];
else tr[u][i] = tr[fa[u]][i];
}
rep(i,1,tot) G[fa[i]].pb(i);
dfs(0);
for(int i=m,u=0;i>=1;i--) u=tr[u][s[i]-33] , pos[i] = u;
for(int i=0,u=0;i+K<=m;i++) v2[u].pb(i),u=tr[u][s[i+1]-33];
dfs2(0);
rep(i,1,n) printf("%d\n",ans[i]);
}