字符串做题记录

[Codechef REBXOR] Nikitosh and xor

解法

由于异或满足 aa=0,故可以将 i=lrai 写成 (i=1rai)(i=1l1ai) 的形式。维护前缀异或和,然后问题转换成了找出两对异或最大的异或和,满足两对异或和对应的区间不交。

使用 01-Trie 维护前缀/后缀的(前缀)异或和,在插入某个异或和后找异或其最大的异或和并将之计入答案,可得前缀/后缀异或和中异或最大的一对。合并两个答案即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=400010;
const int maxl=30;
int n,i,j,t=1,u,v,p,s,l,ans;
int a[maxn],pr[maxn];
int trie[maxn*maxl][2];
bool o;
int main(){
scanf("%d",&n);
for(t=1;t<=maxl;++t) trie[t][0]=t+1;
--t;
for(i=1;i<=n;++i){
scanf("%d",a+i);
a[i]^=a[i-1];
u=p=1;
for(j=maxl-1;j>=0;--j){
o=(a[i]>>j)&1; v=trie[u][o];
if(!v) v=trie[u][o]=++t;
u=v; v=trie[p][!o];
if(v){
p=v;
pr[i]|=(1<<j);
}
else p=trie[p][o];
}
pr[i]=max(pr[i],pr[i-1]);
}
memset(trie,0,sizeof(trie));
t=u=1;
for(j=maxl-1;j>=0;--j){
o=(a[n]>>j)&1;
trie[u][o]=++t;
u=t;
}
for(i=n-1;i;--i){
u=p=1;s=0;
for(j=maxl-1;j>=0;--j){
o=(a[i]>>j)&1; v=trie[u][o];
if(!v) v=trie[u][o]=++t;
u=v; v=trie[p][!o];
if(v){
p=v;
s|=(1<<j);
}
else p=trie[p][o];
}
l=max(l,s);
ans=max(ans,pr[i]+l);
}
printf("%d",ans);
return 0;
}

P2375 [NOI2014] 动物园

解法

考虑满足同时为串 s 的前缀和后缀的(非空)子串有怎样的性质。

如果某个串 s 的前缀同时是其的后缀,则这个子串一定同时为 s1snxtn 的前缀和后缀(或是其本身);而若其长度小于 nxtn,则其长度最多为 nxtnxtn,其又是 s1snxtnxtn 的前缀和后缀;以此类推,可得这个子串的长度一定在 {n,nxtn,nxtnxtn,,1}集合中。

这样来说单独求一个 numi 也是简单的,直接不断跳 nxt 直到跳到得一个长度不大于 i2 处。而求单个 num 的时间复杂度为 O(n);需要在能够保证的时间复杂度内求出每一个 num,可以使用倍增法,一次性跳 2knxt 再进行检查,这样单次求 num 的时间复杂度为 O(logn)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=22;
const int maxn=1000010;
const int md=1000000007;
int t,n,i,j,k,a,p,m;
int dep[maxn],nxt[maxl][maxn];
char s[maxn];
int main(){
scanf("%d",&t);
dep[0]=-1;
while(t--){
scanf("%s",s+1);
n=strlen(s+1);j=0;
for(i=2;i<=n;++i){
while(j&&(s[j+1]!=s[i])) j=nxt[0][j];
if(s[j+1]==s[i]) nxt[0][i]=++j;
else nxt[0][i]=0;
dep[i]=dep[nxt[0][i]]+1;
}
for(i=1;i<maxl;++i) for(j=1;j<=n;++j) nxt[i][j]=nxt[i-1][nxt[i-1][j]];
k=1;a=1;
for(i=2;i<=n;++i){
p=i;m=0;
for(j=maxl-1;j>=0;--j){
if(nxt[j][p]>k){
m+=(1<<j);
p=nxt[j][p];
}
}
m=dep[i]-m+1;
a=(1LL*a*m)%md;
if(i&1) ++k;
}
printf("%d\n",a);
}
return 0;
}

P2414 [NOI2011] 阿狸的打字机

解法

考虑某个串 S 在另外一个串 T 中出现多少次意味着串 T 中有多少个前缀的后缀是 S。对应在 AC 自动机上即为 S 对应的节点在 next 树上有多少个后代在 T 对应的 Trie 树链上。

把所有询问离线,同时将某个点是否在另一个点的子树上转为某个点是否在某段 dfn 序的一个区间里。这样可以先 dfs 一遍 next 树求 dfn 序,然后 dfs 一遍 Trie 树,在进入某个节点时维护当前节点和其祖先节点的所有 dfn 序(可以用树状数组);最后当某个节点 j 对应了某个询问 (i,j) 时,查询当前维护的所有 dfn 序中有多少个在 i 对应的 dfn 序区间内。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
char s[maxn];
int i,p,c,j,l,r,u,v,m,tot,tim;
int fa[maxn],q[maxn],nxt[maxn],ed[maxn];
int ac[maxn][26],trie[maxn][26];
int h[maxn],nxe[maxn],dfn[maxn],edn[maxn];
int cnt[maxn<<1],hq[maxn],aq[maxn];
struct query{int to,nq;}Q[maxn];
inline void add(int p){
for(;p<=tim;p+=(p&-p)) ++cnt[p];
}
inline void sub(int p){
for(;p<=tim;p+=(p&-p)) --cnt[p];
}
inline int query(int p){
int ret=0;
for(;p;p^=(p&-p)) ret+=cnt[p];
return ret;
}
void dfsn(int p){
dfn[p]=++tim;
for(int lp=h[p];lp;lp=nxe[lp]) dfsn(lp);
edn[p]=++tim;
}
void dfsa(int p){
add(dfn[p]);
int lp,to;
for(lp=hq[p];lp;lp=Q[lp].nq){
to=Q[lp].to;
aq[lp]=query(edn[to])-query(dfn[to]-1);
}
for(lp=0;lp<26;++lp){
to=trie[p][lp];
if(!to) continue;
dfsa(to);
}
sub(dfn[p]);
}
int main(){
scanf("%s",s+1);
for(i=1;s[i];++i){
c=s[i];
if(c=='B') p=fa[p];
else if(c=='P') ed[++j]=p;
else{
c-='a';
if(!trie[p][c]){
trie[p][c]=ac[p][c]=++tot;
fa[tot]=p;
}
p=trie[p][c];
}
}
r=-1;
for(i=0;i<26;++i) if(ac[0][i]) q[++r]=ac[0][i];
while(l<=r){
u=q[l++];
for(i=0;i<26;++i){
v=ac[u][i];
if(!v) ac[u][i]=ac[nxt[u]][i];
else{
nxt[v]=ac[nxt[u]][i];
q[++r]=v;
}
}
}
for(i=1;i<=tot;++i){
nxe[i]=h[nxt[i]];
h[nxt[i]]=i;
}
dfsn(0);
scanf("%d",&m);
for(i=1;i<=m;++i){
scanf("%d%d",&u,&v);
u=ed[u]; v=ed[v];
Q[i]={u,hq[v]};hq[v]=i;
}
dfsa(0);
for(i=1;i<=m;++i) printf("%d\n",aq[i]);
return 0;
}

P3975 [TJOI2015]弦论

解法

对这个串建立 SAM。由于 SAM 上从起点出发的任意一条路径均对应了唯一的子串,所以每个点出发的路径条数就是有某个前缀的子串个数。在 SAM 的转移边组成的 DAG 上拓扑 dp 找到从每个点出发的路径条数。

考虑可重子串的计数。在求出每个节点对应的 endpos 之后,计算某个点开始的子串个数时,不能仅仅将这个点出发的路径条数相加。在 DAG 上从某个点向其前驱转移时,需要加上这个点的 endpos 大小(考虑这个前驱节点只加这一条转移边对应的字符的状态,此时需要取这个节点)。从根节点开始时不能减去对应大小(空串不计入排序)。

最后需要在 SAM 的 DAG 上 dfs 以按位确定答案的前缀。在 dfs 搜索到某个节点时,需要将 k 减去这个节点对应的 endpos(不可重计数则减 1),表示这个对应的前缀对答案造成的贡献,若此时 k 为负则已经求出答案。同时在节点上顺次访问转移边,将 k 减去目前扫描到的所有转移边对应的点的后缀数,直到 k 将在这一次操作时变为负,才不进行这次操作,而是确定答案当前位,并且访问这个子节点。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=500010;
const int maxl=1000010;
char s[maxn];
int o,k,i,j,u,v,l=1,r,te;
int deg[maxl],que[maxl];
int h[maxl],nxt[maxn*3],ver[maxn*3];
int p,q,cl,ch;
int cur,lst=1,tot=1;
struct SamNode{
int lnk,len,siz;
int nxt[26];
long long str,sum;
}N[maxl];
#define lnk(p) N[p].lnk
#define len(p) N[p].len
#define siz(p) N[p].siz
#define nxt(p) N[p].nxt
#define str(p) N[p].str
#define sum(p) N[p].sum
int main(){
scanf("%s",s+1);
N[1].str=1;
for(i=1;s[i];++i){
ch=s[i]-'a';
cur=++tot;
p=lst;
siz(cur)=1;
len(cur)=i;
while(p){
if(nxt(p)[ch]) break;
nxt(p)[ch]=cur;
p=lnk(p);
}
if(!p) lnk(cur)=1;
else{
q=nxt(p)[ch];
if(len(q)==len(p)+1) lnk(cur)=q;
else{
N[cl=++tot]=N[q];
len(cl)=len(p)+1;
siz(cl)=0;
lnk(q)=lnk(cur)=cl;
while(p&&nxt(p)[ch]==q){
nxt(p)[ch]=cl;
p=lnk(p);
}
}
}
lst=cur;
}
scanf("%d%d",&o,&k);
if(!o) for(i=1;i<=tot;++i) siz(i)=1;
else{
for(i=2;i<=tot;++i) ++deg[lnk(i)];
for(i=2;i<=tot;++i) if(!deg[i]) que[++r]=i;
while(l<=r){
u=que[l++]; v=lnk(u);
siz(v)+=siz(u);
if(!(--deg[v])) que[++r]=v;
}
}
for(i=1;i<=tot;++i) str(i)=siz(i);
str(1)=siz(1)=r=0;l=1;
for(i=1;i<=tot;++i){
for(j=0;j<26;++j){
if(nxt(i)[j]){
++deg[i];
ver[++te]=i;
nxt[te]=h[nxt(i)[j]];
h[nxt(i)[j]]=te;
}
}
}
for(i=1;i<=tot;++i) if(!deg[i]) que[++r]=i;
while(l<=r){
u=que[l++];
for(i=h[u];i;i=nxt[i]){
v=ver[i]; str(v)+=str(u);
if(!(--deg[v])) que[++r]=v;
}
}
if(k>str(1)){
printf("-1");
return 0;
}
u=1;
for(;;){
if(k<=siz(u)) return 0;
k-=siz(u);
for(i=0;i<26;++i){
v=nxt(u)[i];
if(!v) continue;
if(str(v)<k) k-=str(v);
else{
putchar(i+'a');
u=v; break;
}
}
}
}

P3193 [HNOI2008]GT考试

解法

n 比较小时考虑 dp。设 dpi,j 为当前确定了长为 i 的前缀,这个前缀的后缀与 s 最多能匹配 j 位的方案数。转移时,考虑在 s1sj 后接某个数字 c 能够和 s 最多匹配到多少位,记最多能匹配到 nxtj,c 位(这个值可以通过 KMP 求得的 nxt 数组求出)。同时记 M(j,k)=i=09[nxtj,i=k],则 dpi+1,j=k=0m1M(k,j)dpi,k。 初值有 dp1,0=9,dp1,1=1

考虑矩阵乘法。定义 m×m 的转移矩阵 T,其中 Ti,j=M(i,j)(下标从 0 开始);则由 dp 定义式得:[dpi,0dpi,1dpi,m1]×T=[dpi+1,0dpi+1,1dpi+1,m1]。求 Tn1 即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxm=22;
int n,m,k,i,j,p,c,ans;
int nxt[maxm];
char s[maxm];
struct matrix{
int mat[maxm][maxm];
inline void operator *=(const matrix &a){
matrix tmp;
for(i=0;i<m;++i){
for(j=0;j<m;++j){
for(p=0;p<m;++p) c+=mat[i][p]*a.mat[p][j];
tmp.mat[i][j]=c%k; c=0;
}
}
*this=tmp;
}
}d,r;
int main(){
scanf("%d%d%d%s",&n,&m,&k,s+1); --n;
d.mat[0][1]=d.mat[1][2]=
r.mat[0][0]=r.mat[1][1]=1;
d.mat[0][0]=9;
if(s[1]==s[2]) d.mat[1][0]=9;
else{
d.mat[1][0]=8;
d.mat[1][1]=1;
}
for(i=2;i<m;++i){
while(j&&s[j+1]!=s[i]) j=nxt[j];
if(s[j+1]==s[i]) ++j; nxt[i]=j;
p=i; c=10; r.mat[i][i]=1;
for(;;){
if(!((ans>>(s[p+1]-'0'))&1)){
d.mat[i][p+1]=1;
ans|=1<<(s[p+1]-'0');
--c;
}
if(!p) break;
p=nxt[p];
}
ans=0; d.mat[i][0]=c;
}
c=0;
do{
if(n&1) r*=d;
d*=d;
}while(n>>=1);
ans=r.mat[0][0]*9;
if(m>1){
ans+=r.mat[1][0];
for(i=1;i<m;++i) ans+=r.mat[1][i]+9*r.mat[0][i];
}
printf("%d",ans%k);
return 0;
}

P3715 [BJOI2017]魔法咒语

解法

考虑 dp。建出所有非法串的 AC 自动机之后,则每个非法串的末尾节点和它们在 fail 树上的祖先都是在匹配时不能经过的节点。设 dpi,j 为长为 i 且在自动机上匹配到节点 j 的字符串数量,转移时设 toi,j 为基本串 si 在自动机上从 j 开始能匹配到的节点(可能在中间走过了非法节点,此时 toi,j 不存在),则转移有 k,dpi,jdpi+|sk|,tok,j

考虑 L 很大而 maxk=1n|sk| 很小的情况。此时每个 dpi,j 只需要借助 dpi1,dpi2 计算,可以使用矩阵快速幂优化转移。注意 maxk=1n|sk| 较大时不能用矩阵。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=55;
const int maxl=110;
const int maxb=220;
const int md=1000000007;
int n,m,l,i,j,u,v,c,pt,tot;
int le[maxn],trie[maxl][26];
int nxt[maxl],to[maxn][maxl],dp[maxl][maxl];
char s[maxn][maxl],t[maxl];
bool vis[maxl]; queue<int> q;
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
struct mat{
int m[maxb][maxb];
inline mat operator *(const mat &a){
mat ret;
for(int i=0;i<=v;++i){
for(int j=0;j<=v;++j){
int u=0;
for(int k=0;k<=v;++k)
u=(1LL*m[i][k]*a.m[k][j]+u)%md;
ret.m[i][j]=u;
}
}
return ret;
}
}R,D;
int main(){
scanf("%d%d%d",&n,&m,&l);
for(i=1;i<=n;++i){
scanf("%s",s[i]+1);
le[i]=strlen(s[i]+1);
}
for(i=1;i<=m;++i){
scanf("%s",t+1); pt=0;
for(j=1;c=t[j];++j){
c-='a';
if(!trie[pt][c])
trie[pt][c]=++tot;
pt=trie[pt][c];
}
vis[pt]=1;
}
for(j=0;j<26;++j) if(u=trie[0][j]) q.push(u);
while(!q.empty()){
u=q.front(); q.pop();
for(j=0;j<26;++j){
pt=trie[nxt[u]][j];
if(!(v=trie[u][j]))
trie[u][j]=pt;
else{
nxt[v]=pt;
q.push(v);
}
}
}
for(i=0;i<=tot;++i)
for(j=0;j<=tot;++j)
vis[j]|=vis[nxt[j]];
for(i=1;i<=n;++i){
for(u=0;u<=tot;++u){
if(vis[u]){
to[i][u]=-1;
continue;
}
pt=u;
for(j=1;c=s[i][j];++j){
pt=trie[pt][c-'a'];
if(vis[pt]){pt=-1;break;}
}
to[i][u]=pt;
}
}
if(l>1){
c=*max_element(le+1,le+n+1);
v=tot<<1|1;
for(j=0;j<=tot;++j) D.m[j][tot+j+1]=1;
for(j=0;j<=v;++j) R.m[j][j]=1;
for(i=1;i<=n;++i){
u=(le[i]-1)*(tot+1);
for(j=0;j<=tot;++j)
if(~to[i][j])
++D.m[j+u][to[i][j]];
}
++i;
do{
if(l&1) R=R*D;
D=D*D;
}while(l>>=1);
for(i=u=0;i<=tot;++i) Add(u,R.m[0][i]);
}
else{
dp[0][0]=1;
for(i=0;i<l;++i)
for(u=0;u<=tot;++u)
for(j=1;j<=n;++j)
if((c=i+le[j])<=l&&(~(v=to[j][u])))
Add(dp[c][to[j][u]],dp[i][u]);
for(i=u=0;i<=tot;++i) Add(u,dp[l][i]);
}
printf("%d",u);
return 0;
}

P5840 [COCI2015]Divljak

解法

将所有给出串建 AC 自动机后,考虑在新加一个串时将这个串在 AC 自动机上面进行匹配,标记经过的节点,每次查询多少个串包含了某个串 si,就是哪些串匹配时访问到 si 结尾节点在 fail 树上的子树内。

考虑访问到的节点是否在某个节点的子树内可以想到 dfn + 树状数组,标记某些节点和它们在 fail 树上的节点的祖先的并也是一个经典问题,只需要将所有访问节点离线下来,按照 dfn 排序,设排序后的节点为 u1,u2,,则 i>1,每次新标记 sonlca(ui1,ui)ui 上的点即可。这个问题可以转成链上加 + 单点求和的形式,可以转成单点加 + 子树求和(dfn 上区间求和)的形式。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=22;
const int maxm=100010;
const int maxn=2000010;
const int maxb=maxn<<1;
int n,m,i,j,u,v,o,p=1,ch,tot,tim;
int trie[maxn][26],nxt[maxn];
int dep[maxn],dfn[maxn],edn[maxn];
int lb[maxb],st[maxl][maxb],to[maxm];
int he[maxn],ne[maxn],c[maxb],pe[maxn];
char s[maxn]; queue<int> q;
void dfs(int p,int d){
dep[p]=d;
st[0][++tim]=p; dfn[p]=tim;
for(int to=he[p];to;to=ne[to]){
dfs(to,d+1);
st[0][++tim]=p;
}
edn[p]=tim;
}
inline bool cmp(const int &x,const int &y){return dfn[x]<dfn[y];}
inline int lca(int x,int y){
x=dfn[x]; y=dfn[y]; if(x>y) swap(x,y);
int le=lb[y-x+1];
int u=st[le][x],v=st[le][y-(1<<le)+1];
if(dep[u]>dep[v]) u=v; return u;
}
inline void Add(int p,int x){
for(;p<=tim;p+=p&-p) c[p]+=x;
}
inline void Que(){
int ret=0,l=dfn[u]-1,r=edn[u];
for(;r;r^=r&-r) ret+=c[r];
for(;l;l^=l&-l) ret-=c[l];
printf("%d\n",ret);
}
int main(){
for(i=2;i<maxb;++i) lb[i]=lb[i>>1]+1;
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%s",s+1); p=0;
for(j=1;ch=s[j];++j){
ch-='a';
if(!trie[p][ch])
trie[p][ch]=++tot;
p=trie[p][ch];
}
to[i]=p;
}
for(j=0;j<26;++j) if(u=trie[0][j]) q.push(u);
while(!q.empty()){
u=q.front(); q.pop();
for(j=0;j<26;++j){
p=trie[nxt[u]][j];
if(v=trie[u][j]){
nxt[v]=p;
q.push(v);
}
else trie[u][j]=p;
}
}
for(i=1;i<=tot;++i){
u=nxt[i];
ne[i]=he[u];
he[u]=i;
}
dfs(0,1);
for(j=1;j<maxl;++j){
m=tim-(1<<j)+1;
for(i=1;i<=m;++i){
u=st[j-1][i];
v=st[j-1][i+(1<<(j-1))];
if(dep[u]>dep[v]) u=v;
st[j][i]=u;
}
}
scanf("%d",&m);
while(m--){
scanf("%d",&o);
if(o==1){
scanf("%s",s+1);
for(n=1;ch=s[n];++n)
pe[n]=trie[pe[n-1]][ch-'a'];
sort(pe+1,pe+n,cmp);
Add(dfn[v=pe[1]],1);
for(i=2;i<n;++i){
u=pe[i];
Add(dfn[lca(u,v)],-1);
Add(dfn[u],1); v=u;
}
}
else{
scanf("%d",&u);
u=to[u]; Que();
}
}
return 0;
}

P2336 [SCOI2012]喵星球上的点名

解法

考虑第一问中“某个串作为了多少个给定串的子串”的内容。可以对所有点名串建 AC 自动机,然后将每个名字串在自动机上进行匹配,可以按照上述 P5840 [COCI2015]Divljak 的方法统计答案。

第二问中“某个串有多少个给定串作为子串”,就是将名字串在 AC 自动机上匹配的时候,经过的所有点到 fail 树上的根节点的路径并内有多少个节点为给定串的末端节点。可以直接按照上述统计方法 + 树上差分统计答案。

在建 AC 自动机的时候,转移边数量可能很多,不能直接用 map 存储,可以使用 map 预处理 Trie 树上原有的转移边方便处理,使用主席树维护每个点的转移边,在建某个节点 u 的转移边时只需要用 u 在 Trie 树上的转移边覆盖 failu 的转移边即可,该部分时空复杂度为 O(|Trie|logΣ),其中 Σ 为字符集大小。(似乎有其他的正确复杂度的做法)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=20;
const int maxb=50010;
const int maxn=100010;
const int INF=10000;
int n,m,i,j,u,v,p,a,tp,pt,lt,rt,mt,te=1,tim,tot;
int fa[maxl][maxn],he[maxn],ne[maxn];
int c[maxn],st[maxb],val[maxn],ans[maxb];
int rd[maxn],dep[maxn],dfn[maxn],edn[maxn];
int to[maxb],pe[maxb*3],ph[maxn],nxt[maxn];
inline bool cmp(const int &x,const int &y){return dfn[x]<dfn[y];}
queue<int> q;
map<int,int> trie[maxn];
#define fi first
#define se second
struct seg{int ls,rs;}tr[maxn*maxl];
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
void dfs(int p,int d,int v){
dep[p]=d; val[p]=v; dfn[p]=++tim;
for(int to=he[p];to;to=ne[to])
fa[0][to]=p,dfs(to,d+1,v+val[to]);
edn[p]=tim;
}
inline int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int j=maxl-1;~j;--j) if(dep[fa[j][x]]>=dep[y]) x=fa[j][x];
if(x==y) return x;
for(int j=maxl-1;~j;--j) if(fa[j][x]!=fa[j][y]) x=fa[j][x],y=fa[j][y];
return fa[0][x];
}
inline void Add(int p,int x){
for(;p<=tim;p+=p&-p) c[p]+=x;
}
inline void Que(){
int ret=to[i];
int l=dfn[ret]-1,r=edn[ret];
for(ret=0;r;r^=r&-r) ret+=c[r];
for(;l;l^=l&-l) ret-=c[l];
printf("%d\n",ret);
}
int main(){
scanf("%d%d",&n,&m);
for(i=p=1;i<=n;++i){
st[i]=p;scanf("%d",&u);
while(u--){scanf("%d",&v);pe[p++]=v;}
pe[p++]=-1;scanf("%d",&u);
while(u--){scanf("%d",&v);pe[p++]=v;}
}
st[n+1]=p;
for(i=1;i<=m;++i){
scanf("%d",&u); p=0;
while(u--){
scanf("%d",&v);
if(!trie[p][v]) trie[p][v]=++tot;
p=trie[p][v];
}
++val[p]; to[i]=p;
}
rd[0]=1;
for(auto it:trie[0]){
u=it.fi; v=it.se;
lt=0; pt=1; rt=INF;
while(lt<rt){
mt=(lt+rt)>>1;
if(u<=mt){
if(!ls(pt)) ls(pt)=++te;
pt=ls(pt); rt=mt;
}
else{
if(!rs(pt)) rs(pt)=++te;
pt=rs(pt); lt=mt+1;
}
}
rs(pt)=v; q.push(v);
}
while(!q.empty()){
u=q.front(); q.pop();
rd[u]=++te; tr[te]=tr[p=rd[nxt[u]]];
for(auto it:trie[u]){
lt=0; rt=INF;
pt=p; v=it.fi;
while(lt<rt){
mt=(lt+rt)>>1;
if(v<=mt) pt=ls(pt),rt=mt;
else pt=rs(pt),lt=mt+1;
if(!pt) break;
}
if(pt) nxt[it.se]=rs(pt);
lt=0; rt=INF; pt=rd[u];
while(lt<rt){
mt=(lt+rt)>>1; ++te;
if(v<=mt){
tr[te]=tr[ls(pt)]; ls(pt)=te;
pt=ls(pt); rt=mt;
}
else{
tr[te]=tr[rs(pt)]; rs(pt)=te;
pt=rs(pt); lt=mt+1;
}
}
v=rs(pt)=it.se; q.push(v);
}
}
for(i=1;i<=tot;++i){
u=nxt[i];
ne[i]=he[u];
he[u]=i;
}
dfs(0,1,0);
for(j=1;j<maxl;++j)
for(i=1;i<=tot;++i)
fa[j][i]=fa[j-1][fa[j-1][i]];
for(i=1;i<=n;++i){
p=0; tp=1; u=st[i+1];
for(j=st[i];j<u;++j){
if((v=pe[j])<0){p=0;goto ed;}
lt=0; rt=INF; pt=rd[p];
while(lt<rt){
mt=(lt+rt)>>1;
if(v<=mt) pt=ls(pt),rt=mt;
else pt=rs(pt),lt=mt+1;
if(!pt) break;
}
if(pt) p=rs(pt); else p=0;
ed: ph[++tp]=p;
}
sort(ph+1,ph+tp+1,cmp); u=a=0;
for(j=2;j<=tp;++j){
v=ph[j]; p=lca(u,v);
a+=val[v]-val[p];
Add(dfn[p],-1);
Add(dfn[v],1); u=v;
}
ans[i]=a;
}
for(i=1;i<=m;++i) Que();
for(i=1;i<=n;++i) printf("%d ",ans[i]);
return 0;
}

P3167 [CQOI2014]通配符匹配

解法

考虑将匹配串按照星号位置分成若干段,然后每一段分别和文件名进行匹配,每匹配好一段则下一段直接从该位置开始匹配(显然每一段越早完整匹配越好)。但是这样做显然行不通,因为在有通配符 ? 的情况下不能使用普通的 KMP 解决该问题。

考虑将匹配串按照问号一起分开。然后使用哈希判断某个位置能匹配到哪些位置。设 dpi,ji 位置能否匹配到 j 部分串 sj 的结尾处 的问号,则转移有 dpi,j=(hash(i|sj|,i1)=hash(sj))dpi|sj|1,j1。如果某个位置能够匹配完整,则及时从这一部分断掉开始匹配下一部分。然而分开之后有一些连续段不是以问号结尾的,需要特判掉,同时需要调整 dp 方式。通配符数量有限,所以可以这样进行 dp。

注意开头如果存在 * 则可以 i,dpi,0 的初值设为 1;结尾如果存在 * 则可以将所有 dpi 计入答案。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int P=1997;
const int maxk=12;
const int maxn=100010;
int n,m,i,j,k,p,c,u,v,l,ch,ct,th;
int col[maxk],le[maxk];
char s[maxn],t[maxn];
bool cd,dp[maxn][maxk],vis[maxk];
uint64_t H,pw[maxn],hs[maxk],ht[maxn];
int main(){
for(i=pw[0]=1;i<maxn;++i) pw[i]=pw[i-1]*P;
memset(col,-1,sizeof(col));
scanf("%s",s+1);
m=strlen(s+1);
for(i=1;i<=m;++i){
ch=s[i];
if(ch!='*') cd=1;
if(!isalpha(ch)){
if(cd){
hs[++th]=H;
col[th]=c;
le[th]=ct;
if(ch=='*'){++c;cd=0;}
else vis[th]=1;
}
H=ct=0;
continue;
}
H+=pw[ct++]*ch;
}
if(cd){
hs[++th]=H;
col[th]=c;
le[th]=ct;
}
scanf("%d",&n);
while(n--){
scanf("%s",t+1); p=strlen(t+1);
for(i=1;i<=p;++i) ht[i]=ht[i-1]+pw[i-1]*t[i];
memset(dp,0,sizeof(dp));
k=u=0; v=1;
if(s[1]=='*') for(i=0;i<=p;++i) dp[i][0]=1;
else dp[0][0]=1;
for(;k<=c;++k){
for(i=u+1;i<=p;++i){
for(j=v;col[j]==k;++j){
if(vis[j]){
l=i-le[j];
if(l>u&&ht[i-1]-ht[l-1]==hs[j]*pw[l-1])
dp[i][j]|=dp[l-1][j-1];
}
else{
l=i-le[j]+1;
if(l>u&&ht[i]-ht[l-1]==hs[j]*pw[l-1])
dp[i][j]|=dp[l-1][j-1];
}
}
if(dp[i][j-1]&&k<c){u=i;break;}
}
if(i>p&&k<c){u=0;goto st;}
if(k==c) break;
while(col[v]==k) ++v;
for(i=u;i<=p;++i) dp[i][v-1]=1;
}
u=dp[p][th];
if(s[m]=='*') for(i=p;i;--i) u|=dp[i][th];
st: if(u) printf("YES\n"); else printf("NO\n");
}
return 0;
}

DarkBzoj2741 【FOTILE模拟赛】L

解法

k=ijak 拆成 (k=1i1ak)(k=1jak) 的形式,则问题转化成了求 maxl1i<jr{(k=1iak)(k=1jak)},也就是区间选两个数满足异或值最大。这个问题可以使用 01-Trie,每次加入一个数时查询与这个数异或最大的数,同时插入这个数,在 O(nloga) 时间内解决。

考虑多组在线询问的情况,我们可以预处理其中一部分。

将序列(此时序列最左端需要插入 0,表示空前缀)分成长为 S 的块,然后预处理从每一块开头到任意位置的答案,记 blki,j 为从第 i 块开头到 j 这个区间的任意两数最大异或和。此时在询问某个区间 [l,r] 时(只讨论 [l,r] 在不同块的情形,否则直接暴力 O(S) 求),先找出 l1 之后的第一个整块(令其为第 i 个),此时需要查询 blki,r 和对应的 Trie,然后在这个 Trie 上暴力插入 l1 到第 i 块开头前的所有异或和并查询与它们异或最大的数,然后更新答案。

查询某个序列的任意前缀对应的 Trie 可以用可持久化 Trie,然后查询从任意位置 l 开始的序列的任意前缀 lr 可以把两个时刻的 Trie 相减(只统计 lr 时刻更新的节点,在统计 r 时刻的 Trie 中部统计出现在 l 中的节点)。注意由于原题空间限制,不能暴力开 O(nS) 个可持久化 Trie。

时间复杂度为 O((n2S+mS)loga),此处时间限制很宽,S 设为 n 可以通过。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=32;
const int maxb=120;
const int maxn=12010;
int n,m,i,j,k,c,s,v,x,y,l,r,pt,px,tmp,tot,lst,ans;
bool f;
int a[maxn],rt[maxn],blk[maxb][maxn];
int tr[(maxn+maxb)*(maxl+3)][2];
int main(){
scanf("%d%d",&n,&m);
s=sqrt(n);
for(i=1;i<=n;++i){
scanf("%d",a+i);
a[i]^=a[i-1];
}
for(i=0;i<=n;++i){
pt=lst; rt[i]=++tot;
tr[tot][0]=tr[lst][0];
tr[tot][1]=tr[lst][1];
lst=tot; v=a[i];
for(k=maxl-1;k>=0;--k){
f=(v>>k)&1;
++tot; pt=tr[pt][f];
tr[tot][0]=tr[pt][0];
tr[tot][1]=tr[pt][1];
tr[tot-1][f]=tot;
}
}
for(i=c=0;i<=n;i+=s,++c){
for(j=i;j<=n;++j){
v=a[j];
if(i) pt=rt[i-1];
else pt=0;
px=rt[j]; tmp=0;
for(k=maxl-1;k>=0;--k){
f=(v>>k)&1;
if(tr[pt][!f]!=tr[px][!f]){
tmp|=1<<k;
pt=tr[pt][!f];
px=tr[px][!f];
}
else{
pt=tr[pt][f];
px=tr[px][f];
}
}
blk[c][j]=ans=max(ans,tmp);
}
ans=0;
}
j=tot;
while(m--){
scanf("%d%d",&x,&y);
l=(((long long)x)+ans)%n+1;
r=(((long long)y)+ans)%n+1;
if(l>r) swap(l,r);
c=(--l)/s+1;
y=min(r,c*s-1);
ans=blk[c][r];
lst=rt[r];
for(i=l;i<=y;++i){
v=a[i]; ++tot; tmp=0;
tr[tot][0]=tr[lst][0];
tr[tot][1]=tr[lst][1];
x=px=lst; lst=tot;
if(l) pt=rt[l-1];
else pt=0;
for(k=maxl-1;k>=0;--k){
f=(v>>k)&1;
++tot; x=tr[x][f];
tr[tot][0]=tr[x][0];
tr[tot][1]=tr[x][1];
tr[tot-1][f]=tot;
if(tr[pt][!f]!=tr[px][!f]){
tmp|=1<<k;
pt=tr[pt][!f];
px=tr[px][!f];
}
else{
pt=tr[pt][f];
px=tr[px][f];
}
}
ans=max(ans,tmp);
}
printf("%d\n",ans);
tot=j;
}
return 0;
}
posted @   Fran-Cen  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示