CWOI 字符串专题

A - Indie Album

考虑离线,对询问串跑 AC 自动机,建出 fail 树。再把题目中那个版本继承关系建成一棵树,在这棵树上 dfs,进入一个点的时候在 fail 树上单点加,走的时候减掉,维护子树求和即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[400005];
int tot,head[400005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int I,ch[400005][28],fail[400005];
void build(){
	queue<int>q;
	for(int i=0;i<26;i++)if(ch[0][i])q.push(ch[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();add(fail[u],u);
		for(int i=0;i<26;i++){
			if(ch[u][i])fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
			else ch[u][i]=ch[fail[u]][i];
		}
	}
}
int cur,dfn[400005],rnk[400005],siz[400005];
vector<int>g[400005],t[400005];
struct BIT{
	int c[400005];
	void add(int x,int v){
		for(;x<=cur;x+=x&-x)c[x]+=v;
	}
	void add(int l,int r,int v){
		add(l,v),add(r+1,-v);
	}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res+=c[x];
		return res;
	}
	int ask(int l,int r){
		return ask(r)-ask(l-1);
	}
}Tr;
int p[400005],ans[400005],endpos[400005];
void dfs1(int u){
	dfn[u]=++cur,rnk[cur]=u,siz[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		dfs1(v);siz[u]+=siz[v];
	}
}
void dfs2(int u){
	Tr.add(dfn[p[u]],1);
	for(auto x:t[u])ans[x]=Tr.ask(dfn[endpos[x]],dfn[endpos[x]]+siz[endpos[x]]-1);
	for(auto v:g[u])dfs2(v);
	Tr.add(dfn[p[u]],-1);
}
int op[400005];int x[400005],q[400005];char c[400005][5];string s[400005];
signed main(){
	int n=read();
	for(int i=1;i<=n;i++){
		op[i]=read();
		if(op[i]==1)scanf("%s",c[i]),g[0].push_back(i);
		else x[i]=read(),scanf("%s",c[i]),g[x[i]].push_back(i);
	}
	int m=read();
	for(int i=1;i<=m;i++){
		q[i]=read();cin>>s[i];
	}
	for(int i=1;i<=m;i++){
		int u=0;
		for(int j=0;j<(int)s[i].size();j++){
			if(!ch[u][s[i][j]-'a'])ch[u][s[i][j]-'a']=++I;
			u=ch[u][s[i][j]-'a'];
		}
		endpos[i]=u,t[q[i]].push_back(i);
	}
	build();
	for(int i=1;i<=n;i++){
		if(op[i]==1)p[i]=ch[0][c[i][0]-'a'];
		else p[i]=ch[p[x[i]]][c[i][0]-'a'];
	}
	dfs1(0);dfs2(0);
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return 0;
}

B - 通配符匹配

做烦了。

考虑按 * 把整个串分开,称为大段。把大段按 ? 分开,称为小段。先直接匹配掉最左和最右的小段。考虑安排每个大段的位置。发现越靠左越好,能为后面留下更多空间,反正中间可以用 * 一次匹配完。怎么对大段找匹配的位置呢?考虑对所有小段建 AC 自动机,从左往右扫过去,找到在 fail 树上对应的节点,然后枚举当前大段内的所有小段,如果小段能匹配当前位置的一段后缀那么就找到如果这么匹配,这个大段的开头在哪里,桶计数器加一。如果一个位置计数到了小段个数那么就说明它作为开头是合法的了。时间复杂度 \(\mathcal{O}(n|\sum|+k\sum len)\)

细节比较多,比如没有 * 的情况要特判。其实做复杂了,只需要判断两段字符串是否相等,hash 就行。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int I,ch[100005][28],fail[100005];vector<int>g[100005];
void build(){
	queue<int>q;
	for(int i=0;i<26;i++)if(ch[0][i])q.push(ch[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();g[fail[u]].push_back(u);
		for(int i=0;i<26;i++){
			if(ch[u][i])fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
			else ch[u][i]=ch[fail[u]][i];
		}
	}
}
int cur,dfn[100005],rnk[100005],siz[100005];
void dfs(int u){
	dfn[u]=++cur,rnk[cur]=u,siz[u]=1;
	for(auto v:g[u])dfs(v),siz[u]+=siz[v];
}
int n,m,endpos[15][15],check_pos[100005];
vector<int>star,ques[15];char s[100005],t[100005];
int ask(){
	if(star.empty()){
		if(n!=m)return 0;
		for(int i=1,j=1;i<=n;i++,j++){
			if(j>m)return 0;
			if(s[i]=='?'||s[i]==t[j])continue;
			return 0;
		}
		return 1;
	}
	for(int i=1,j=1;i<=star.front()-1;i++,j++){
		if(j>m)return 0;
		if(s[i]=='?'||s[i]==t[j])continue;
		return 0;
	}
	int pos=star.front();
	for(int i=0;i+1<(int)star.size();i++){
		if(star[i]+1>star[i+1]-1)continue;
		for(int j=1;j<=m;j++)check_pos[j]=0;
		int u=0,cnt=0;
		for(int k=0;k+1<(int)ques[i].size();k++){
			if(ques[i][k]+1<=ques[i][k+1]-1)cnt++;
		}
		for(int j=pos;j<=m;j++){
			u=ch[u][t[j]-'a'];
			for(int k=0;k+1<(int)ques[i].size();k++){
				if(ques[i][k]+1<=ques[i][k+1]-1&&dfn[endpos[i][k]]<=dfn[u]&&dfn[u]<=dfn[endpos[i][k]]+siz[endpos[i][k]]-1){
					int delta=(ques[i][k+1]-1)-(ques[i][0]+1)+1;
					if(j-delta+1>=1){
						check_pos[j-delta+1]++;
					}
				}
			}
		}
		int flag=0;
		for(int j=pos;j<=m;j++){
			if(check_pos[j]==cnt){
				if(j+((star[i+1]-1)-(star[i]+1)+1)-1>m)return 0;
				pos=j+((star[i+1]-1)-(star[i]+1)+1)-1+1;flag=1;break;
			}
		}
		if(flag)continue;
		return 0;
	}
	for(int i=n,j=m;i>=star.back()+1;i--,j--){
		if(j<1||j<pos)return 0;
		if(s[i]=='?'||s[i]==t[j])continue;
		return 0;
	}
	return 1;
}
signed main(){
	scanf("%s",s+1);n=strlen(s+1);
	for(int i=1;i<=n;i++){
		if(s[i]=='*')star.push_back(i);
	}
	for(int i=0;i+1<(int)star.size();i++){
		ques[i].push_back(star[i]);
		for(int j=star[i]+1;j<=star[i+1]-1;j++){
			if(s[j]=='?')ques[i].push_back(j);
		}
		ques[i].push_back(star[i+1]);
		for(int j=0;j+1<(int)ques[i].size();j++){
			int u=0;
			for(int k=ques[i][j]+1;k<=ques[i][j+1]-1;k++){
				if(!ch[u][s[k]-'a'])ch[u][s[k]-'a']=++I;
				u=ch[u][s[k]-'a'];
			}
			endpos[i][j]=u;
		}
	}
	build();dfs(0);
	int q=read();
	while(q--){
		scanf("%s",t+1);m=strlen(t+1);
		if(ask())puts("YES");
		else puts("NO");
	}
	return 0;
}

C - Death DBMS

对姓名建 AC 自动机,问题变成单点改,查询一个点到根的链上的最大值。大力树剖即可。复杂度 \(\mathcal{O}(n\log^2 n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[300005];
int tot,head[300005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int I,ch[300005][28],fail[300005];
void build(){
	queue<int>q;
	for(int i=0;i<26;i++)if(ch[0][i])q.push(ch[0][i]);
	while(!q.empty()){
		int u=q.front();q.pop();add(fail[u],u);
		for(int i=0;i<26;i++){
			if(ch[u][i])fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
			else ch[u][i]=ch[fail[u]][i];
		}
	}
}
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int mx;
	}c[1200005];
	void pushup(int p){
		c[p].mx=max(c[ls].mx,c[rs].mx);
	}
	void build(int l,int r,int p){
		if(l==r){
			c[p].mx=-1;
			return;
		}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void upd(int l,int r,int p,int x,int k){
		if(l==r){c[p].mx=k;return;}
		int mid=(l+r)>>1;
		if(x<=mid)upd(lson,x,k);
		else upd(rson,x,k);
		pushup(p);
	} 
	int qry(int l,int r,int p,int L,int R){
		if(L>R)return -1;
		if(L<=l&&r<=R)return c[p].mx;
		int mid=(l+r)>>1,res=-1;
		if(L<=mid)res=max(res,qry(lson,L,R));
		if(R>mid)res=max(res,qry(rson,L,R));
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
int siz[300005],son[300005],dep[300005];
void dfs1(int u){
	siz[u]=1,son[u]=-1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;dep[v]=dep[u]+1;
		dfs1(v);siz[u]+=siz[v];
		if(son[u]==-1||siz[son[u]]<siz[v])son[u]=v;
	}
}
int cur,dfn[300005],rnk[300005],top[300005];
void dfs2(int u,int rt){
	top[u]=rt,dfn[u]=++cur,rnk[cur]=u;
	if(son[u]!=-1)dfs2(son[u],rt);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v!=son[u])dfs2(v,v);
	}
}
int endpos[300005],a[300005];string s[300005];multiset<int>S[300005];
int ask(int u,int v){
	int res=-1;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res=max(res,Tr.qry(1,cur,1,dfn[top[u]],dfn[u]));
		u=fail[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	res=max(res,Tr.qry(1,cur,1,dfn[v],dfn[u]));
	return res;
}
signed main(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++){
		int u=0;cin>>s[i];
		for(int j=0;j<(int)s[i].size();j++){
			if(!ch[u][s[i][j]-'a'])ch[u][s[i][j]-'a']=++I;
			u=ch[u][s[i][j]-'a'];
		}
		endpos[i]=u,a[i]=0;
	}
	build();dfs1(0);dfs2(0,0);Tr.build(1,cur,1);
	for(int i=0;i<=I;i++)S[i].insert(-1);
	for(int i=1;i<=n;i++)S[endpos[i]].insert(a[i]),Tr.upd(1,cur,1,dfn[endpos[i]],0);
	while(m--){
		int op=read();
		if(op==1){
			int x=read(),y=read();
			S[endpos[x]].erase(S[endpos[x]].find(a[x]));
			a[x]=y;S[endpos[x]].insert(a[x]);
			Tr.upd(1,cur,1,dfn[endpos[x]],*S[endpos[x]].rbegin());
		}
		else{
			int u=0,res=-1;string q;cin>>q;
			for(int i=0;i<(int)q.size();i++){
				u=ch[u][q[i]-'a'];
				res=max(res,ask(0,u));
			}
			printf("%lld\n",res);
		} 
	}
	return 0;
}

D - 喵星球上的点名

本题正解似乎是广义 sam 或者 sa 之类的玩意,但可以不用。考虑一个经典结论:模式串的长度种类是 \(\sqrt{\sum len}\) 级别的。于是你可以对每种长度开一个哈希表,暴力枚举长度 hash 找模式串就是 \(\mathcal{O}(\sum len\sqrt{\sum len})\) 的了。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N=1e5;
const ull base=13331;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
unordered_map<ull,int>buc[325],tmp[325];
struct Node{
	int len;vector<int>s;vector<ull>h;
}a[2][50005],b[100005];
int n,m,tot,ans1[100005],ans2[50005],B[100005];ull pw[100005];
void solve(int id){
	for(int i=1;i<=tot;i++){
		unordered_map<ull,int>c;
		for(int j=1;j+B[i]-1<=a[0][id].len;j++){
			if(buc[i].count((ull)(a[0][id].h[j+B[i]-1]-a[0][id].h[j-1]*pw[B[i]]))){
				c[(ull)(a[0][id].h[j+B[i]-1]-a[0][id].h[j-1]*pw[B[i]])]=1;
			}
		}
		for(int j=1;j+B[i]-1<=a[1][id].len;j++){
			if(buc[i].count((ull)(a[1][id].h[j+B[i]-1]-a[1][id].h[j-1]*pw[B[i]]))){
				c[(ull)(a[1][id].h[j+B[i]-1]-a[1][id].h[j-1]*pw[B[i]])]=1;
			}
		}
		for(auto x:c)ans2[id]+=buc[i][x.first],tmp[i][x.first]++;
	}
}
signed main(){
	n=read(),m=read();
	pw[0]=1;for(int i=1;i<=N;i++)pw[i]=pw[i-1]*base;
	for(int i=1;i<=n;i++){
		for(int o=0;o<2;o++){
			int k=read();a[o][i].len=k;
			a[o][i].s.resize(k+5);for(int j=1;j<=k;j++)a[o][i].s[j]=read()+1;
			a[o][i].h.resize(k+5);for(int j=1;j<=k;j++)a[o][i].h[j]=a[o][i].h[j-1]*base+a[o][i].s[j];			
		}
	}
	for(int i=1;i<=m;i++){
		int k=read();b[i].len=k;B[++tot]=k;
		b[i].s.resize(k+5);for(int j=1;j<=k;j++)b[i].s[j]=read()+1;
		b[i].h.resize(k+5);for(int j=1;j<=k;j++)b[i].h[j]=b[i].h[j-1]*base+b[i].s[j];
	}
	sort(B+1,B+tot+1);tot=unique(B+1,B+tot+1)-B-1;
	for(int i=1;i<=m;i++){
		b[i].len=lower_bound(B+1,B+tot+1,b[i].len)-B;
		buc[b[i].len][b[i].h[B[b[i].len]]]++;
	}
	for(int i=1;i<=n;i++)solve(i);
	for(int i=1;i<=m;i++)ans1[i]=tmp[b[i].len][b[i].h[B[b[i].len]]];
	for(int i=1;i<=m;i++)printf("%d\n",ans1[i]);
	for(int i=1;i<=n;i++)printf("%d ",ans2[i]);
	return 0;
}

一个正经的 \(\mathcal{O}(\sum len(\log\sum len+\log|\sum|))\) 的做法。

首先一个串可能既在姓里也在名里,考虑把姓名拼起来,中间插一个字符集中不存在的字符,只需要判断一个串在不在我们连成的新串里面就可以不重不漏了。考虑对询问串建 AC 自动机。注意到普通的 AC 自动机建法时空复杂度都是 \(\sum len|\sum|\) 的,这显然不能接受。注意到我们在建 AC 自动机时,一个点 \(u\) 如果有对应转移我们会保留,没有则会继承 \(fail(u)\) 的转移。考虑用主席树维护转移,每次直接把 \(fail(u)\) 的所有转移复制过来,然后再把它自己有的转移改上去。

考虑怎么求答案。第一个问题问有多少个姓名包含给定串。考虑对每个姓名的每个前缀在 fail 树上对应的点找出来,覆盖它们到根的路径,单点查询一个点被覆盖了几次(一个姓名的覆盖只算一次)。先把这个变成单点加子树求和。然后为了不算重我们把一个姓名的所有点按 dfs 序排序,然后减去相邻两点的 lca 的贡献即可。第二个问题可以变成子树加单点求和,也可以用上面的办法减去重复贡献。

似乎还有 GSAM 的做法,看什么时候会了补一下。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e4+1;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct segtree{
	#define ls (c[p].lc)
	#define rs (c[p].rc)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int lc,rc,s;
	}c[4000005];
	int T;
	void pushup(int p){
		c[p].s=c[ls].s+c[rs].s;
	}
	int build(int l,int r){
		int p=++T;
		if(l==r){c[p].s=0;return p;}
		int mid=(l+r)>>1;
		ls=build(l,mid);rs=build(mid+1,r);
		pushup(p);return p;
	}
	int upd(int l,int r,int q,int x,int v){
		int p=++T;c[p]=c[q];
		if(l==r){c[p].s=v;return p;}
		int mid=(l+r)>>1;
		if(x<=mid)ls=upd(l,mid,c[q].lc,x,v);
		else rs=upd(mid+1,r,c[q].rc,x,v);
		pushup(p);return p;
	}
	int ask(int l,int r,int p,int x){
		if(l==r)return c[p].s;
		int mid=(l+r)>>1;
		if(x<=mid)return ask(lson,x);
		else return ask(rson,x);
	}
	#undef lson
	#undef rson
	#undef ls
	#undef rs
}Tr;
int I,root[100005],fail[100005];vector<pii>trans[100005];vector<int>g[100005];
void buildAC(){
	queue<int>q;
	for(int i=0;i<=inf;i++)if(Tr.ask(0,inf,root[0],i))q.push(Tr.ask(0,inf,root[0],i));
	while(!q.empty()){
		int u=q.front();q.pop();g[fail[u]].push_back(u);root[u]=root[fail[u]];
		for(auto v:trans[u]){
			root[u]=Tr.upd(0,inf,root[u],v.fi,v.se);
			fail[v.se]=Tr.ask(0,inf,root[fail[u]],v.fi);
			q.push(v.se);
		}
	}
}
int cur,dfn[100005],f[20][100005],siz[100005];
int getmin(int u,int v){
	return ((dfn[u]<dfn[v])?u:v);
}
void dfs(int u){
	dfn[u]=++cur,f[0][cur]=fail[u],siz[u]=1;
	for(auto v:g[u])dfs(v),siz[u]+=siz[v];
}
int getlca(int u,int v){
	if(u==v)return u;
	if((u=dfn[u])>(v=dfn[v]))swap(u,v);
	int o=__lg(v-u++);
	return getmin(f[o][u],f[o][v-(1ll<<o)+1]);
}
struct BIT{
	int c[100005];
	void clear(){
		for(int i=1;i<=cur;i++)c[i]=0;
	}
	void add(int x,int v){
		for(;x<=cur;x+=x&-x)c[x]+=v;
	}
	void add(int l,int r,int v){
		add(l,v),add(r+1,-v);
	}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res+=c[x];
		return res;
	}
	int ask(int l,int r){
		return ask(r)-ask(l-1);
	}
}bit;
int pos[100005];vector<int>a[100005],b[100005];
signed main(){
	int n=read(),m=read();
	for(int i=1,len;i<=n;i++){
		len=read();
		for(int j=1;j<=len;j++)a[i].push_back(read());
		a[i].push_back(inf);
		len=read();
		for(int j=1;j<=len;j++)a[i].push_back(read());
	}
	for(int i=1,len;i<=m;i++){
		len=read();
		for(int j=1;j<=len;j++)b[i].push_back(read());
	}
	root[0]=Tr.build(0,inf);
	for(int i=1;i<=m;i++){
		int u=0;
		for(auto j:b[i]){
			if(!Tr.ask(0,inf,root[u],j))root[u]=Tr.upd(0,inf,root[u],j,++I),trans[u].push_back({j,I});
			u=Tr.ask(0,inf,root[u],j);
		}
		pos[i]=u;
	}
	buildAC();dfs(0);
	for(int j=1;(1ll<<j)<=cur;j++){
		for(int i=1;i+(1ll<<j)-1<=cur;i++){
			f[j][i]=getmin(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1;i<=n;i++){
		int u=0;vector<int>tmp;
		for(auto j:a[i])u=Tr.ask(0,inf,root[u],j),tmp.push_back(u);
		sort(tmp.begin(),tmp.end(),[](int x,int y){return dfn[x]<dfn[y];});
		for(int j=0;j<(int)tmp.size();j++)bit.add(dfn[tmp[j]],1);
		for(int j=1;j<(int)tmp.size();j++)bit.add(dfn[getlca(tmp[j-1],tmp[j])],-1);
	}
	for(int i=1;i<=m;i++){
		printf("%lld\n",bit.ask(dfn[pos[i]],dfn[pos[i]]+siz[pos[i]]-1));
	}
	bit.clear();
	for(int i=1;i<=m;i++)bit.add(dfn[pos[i]],dfn[pos[i]]+siz[pos[i]]-1,1);
	for(int i=1;i<=n;i++){
		int u=0,res=0;vector<int>tmp;
		for(auto j:a[i])u=Tr.ask(0,inf,root[u],j),tmp.push_back(u);
		sort(tmp.begin(),tmp.end(),[](int x,int y){return dfn[x]<dfn[y];});
		for(int j=0;j<(int)tmp.size();j++)res+=bit.ask(dfn[tmp[j]]);
		for(int j=1;j<(int)tmp.size();j++)res-=bit.ask(dfn[getlca(tmp[j-1],tmp[j])]);
		printf("%lld ",res);
	}
	return 0;
}

F - 双倍回文

考虑 manacher。容易发现因为我们只需要统计最长的双倍回文串,所以只有在当前位置拓展得超过最大右边界时才有可能更新答案,复杂度 \(\mathcal{O}(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int r[1000005];char s[1000005],t[1000005];
signed main(){
	int m=read(),n=0,ans=0,c=0,R=0;scanf("%s",t+1);s[0]='!',s[++n]='#';
	for(int i=1;i<=m;i++)s[++n]=t[i],s[++n]='#';
	for(int i=1;i<=n;i++){
		r[i]=((i<=R)?min(r[2*c-i],R-i+1):0ll);
		while(i-r[i]>=1&&s[i-r[i]]==s[i+r[i]])r[i]++;
		if(i+r[i]-1>R){
			for(int j=R+1;j<=i+r[i]-1;j++){
				if((j&1ll)&&(j-i)%4==0&&r[(i+(i*2-j))/2]>=(j-i)/2)ans=max(ans,j-i);
			}
			c=i,R=i+r[i]-1;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

G - Palindromic Substring

经典结论:串 \(S\) 的本质不同回文子串个数不超过 \(|S|\)。manacher,每次拓展的时候把这些回文串找出来。注意此时这些串可能有重复,需要去重。问题变成有一些区间 \([l,r]\),问 \(s[l\ldots r]\)\(s\) 中出现了几次。对 \(s\) 跑 SA,对于询问 \([l,r]\),向左/右二分出能包含 \([l,r]\) 的最长位置。每次询问把所有串的价值排序即可。复杂度 \(\mathcal{O}(Tmn\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18,mod=777777777;
const ull base=13331;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int rk[200005],sa[200005],c[200005],tp[200005],ht[200005];char s[200005],t[200005];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(int n){
	int m='z';
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=n;i>=n-w+1;i--)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	} 
}
int f[20][200005];
int ask(int l,int r){
	assert(l<=r);
	int o=__lg(r-l+1);
	return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int r[200005],lp[200005],rp[200005],cnt[200005];
int PW[200005],sum[200005],val[30],w[200005],id[200005];
int get(int l,int r){
	return (sum[r]-sum[l-1]*PW[r-l+1]%mod+mod)%mod;
}
ull pw[200005],h[200005];
void solve(){
	int n=read(),m=read(),len=0,tot=0;
	scanf("%s",s+1);t[0]='!',t[++len]='#';
	for(int i=1;i<=n;i++)t[++len]=s[i],t[++len]='#';
	int p=0,R=0;
	for(int i=1;i<=len;i++){
		r[i]=((i<=R)?min(r[2*p-i],R-i+1):0ll);
		while(i-r[i]>=1&&t[i-r[i]]==t[i+r[i]])r[i]++;
		if(i+r[i]-1>R){
			for(int j=R+1;j<=i+r[i]-1;j++){
				if('a'<=t[j]&&t[j]<='z'){
					lp[++tot]=(2*i-j)/2,rp[tot]=j/2,cnt[tot]=0;
				}
			}
			p=i,R=i+r[i]-1;
		}
	}
	pw[0]=1;for(int i=1;i<=n;i++)h[i]=h[i-1]*base+s[i],pw[i]=pw[i-1]*base;
	map<ull,int>vis;vector<int>tmp;
	for(int i=1;i<=tot;i++){
		if(!vis[h[rp[i]]-h[lp[i]-1]*pw[rp[i]-lp[i]+1]]){
			vis[h[rp[i]]-h[lp[i]-1]*pw[rp[i]-lp[i]+1]]=1;
			tmp.push_back(i);
		}
	}
	tot=0;
	for(auto x:tmp)lp[++tot]=lp[x],rp[tot]=rp[x];
	buildSA(n);
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==1){ht[rk[i]]=0;continue;}
		if(k)k--;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	for(int i=1;i<=n;i++)f[0][i]=ht[i];
	for(int j=1;(1ll<<j)<=n;j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1,l,r,res;i<=tot;i++){
		l=rk[lp[i]]+1,r=n,res=rk[lp[i]];cnt[i]=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(ask(rk[lp[i]]+1,mid)>=rp[i]-lp[i]+1)res=mid,l=mid+1;
			else r=mid-1;
		}
		cnt[i]+=res-rk[lp[i]]+1;
		l=1,r=rk[lp[i]]-1,res=rk[lp[i]];
		while(l<=r){
			int mid=(l+r)>>1;
			if(ask(mid+1,rk[lp[i]])>=rp[i]-lp[i]+1)res=mid,r=mid-1;
			else l=mid+1;
		}
		cnt[i]+=rk[lp[i]]-res;
	}
	PW[0]=1;for(int i=1;i<=n;i++)PW[i]=PW[i-1]*26%mod;
	while(m--){
		int k=read();
		for(int i=0;i<26;i++)val[i]=read();
		for(int i=1;i<=n;i++)sum[i]=(sum[i-1]*26%mod+val[s[i]-'a'])%mod;
		for(int i=1;i<=tot;i++)w[i]=get(lp[i],(lp[i]+rp[i])/2);
		for(int i=1;i<=tot;i++)id[i]=i;
		sort(id+1,id+tot+1,[](int x,int y){return w[x]<w[y];});
		int flag=0;
		for(int i=1;i<=tot;i++){
			if(k>cnt[id[i]])k-=cnt[id[i]];
			else{printf("%lld\n",w[id[i]]);flag=1;break;}
		}
		if(!flag)puts("0");
	}
	for(int i=0;i<=len;i++)rk[i]=sa[i]=tp[i]=ht[i]=f[0][i]=0;
	for(int i=0;i<=len;i++)r[i]=lp[i]=rp[i]=cnt[i]=w[i]=pw[i]=PW[i]=h[i]=sum[i]=id[i]=t[i]=0;
	puts("");
}
signed main(){
	int T=read(); 
	while(T--){
		solve();
	}
	return 0;
}

H - Palindromic Equivalence

牛牛题。考虑 manacher,对于一个回文串 \([l,r]\),注意到它能提供的信息是回文串内对应的位置在新串内也要相等,然后 \(l-1\)\(r+1\) 不等。相等关系我们并查集合并,不等关系我们先放着。容易发现只有在 manacher 拓展的时候才用合并。

对于并查集的一个连通块,考虑其中编号最小的一个点,将这个点的编号当成这个连通块的编号。假如现在 \(i<j<k\) 三个连通块满足 \((i,k)\)\((j,k)\) 间都有不等关系,注意到此时 \((i,j)\) 间也一定有不等关系。证明就是你发现 \((i,k)\)\((j,k)\) 间都是一个极长的回文串,所以你把 \((j,k)\) 翻到左边,记作 \(j'\)。大概长这样:

但是你注意到如果长成这样的话那么 \(j'\)\(j\) 实际上是相等关系,如果此时 \(i\)\(j\) 也是相等关系的话,那左边就不是一个极长的回文串,与定义不符。还有一种情况是:

这其实也是能得到 \(j'\)\(j\) 是相等关系,所以 \(i\)\(j\) 只能是不等关系。

综上所述,我们从小到大枚举每个连通块 \(i\),看它和之前的哪些连通块有连边,记为 \(S\)。那么根据上述结论,\(i\)\(S\) 中所有连通块的颜色都不同,所以选法就是 \(26-|S|\)。复杂度 \(\mathcal{O}(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int fa[1000005],r[2000005],vis[1000005];vector<int>g[1000005];char s[2000005],t[2000005];
int find(int x){
	return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
void merge(int x,int y){
	if((x=find(x))==(y=find(y)))return;
	fa[y]=x;
}
signed main(){
	scanf("%s",t+1);int m=strlen(t+1),n=0,ans=1;s[0]='!',s[++n]='#';
	for(int i=1;i<=m;i++)s[++n]=t[i],s[++n]='#',fa[i]=i;
	vector<pair<int,int> >tmp;
	for(int i=1,c=0,R=0;i<=n;i++){
		r[i]=((i<=R)?min(r[2*c-i],R-i+1):0ll);
		while(i-r[i]>=1&&i+r[i]<=n&&s[i-r[i]]==s[i+r[i]]){
			if((i-r[i])%2==0)merge((i-r[i])/2,(i+r[i])/2);
			r[i]++;
		}
		if(i-r[i]>=1&&i+r[i]<=n)tmp.push_back({(i-r[i])/2,(i+r[i])/2});
		if(i+r[i]-1>R)c=i,R=i+r[i]-1;
	}
	for(auto x:tmp){
		int u=min(find(x.first),find(x.second));
		int v=max(find(x.first),find(x.second));
		g[v].push_back(u);
	}
	for(int i=1;i<=m;i++){
		if(find(i)==i){
			int cnt=26;
			for(auto x:g[i])if(!vis[x])cnt--,vis[x]=1;
			for(auto x:g[i])vis[x]=0;
			ans=ans*cnt%mod;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

I - 不同子串个数

考虑直接跑 SA,然后求 height 数组。这个不同子串个数其实就是对每个后缀建 trie,求 trie 的大小。考虑从小到大枚举 \(i\),考虑相邻的 \(sa(i-1),sa(i)\) 的 lca 的深度是 \(height(i)\),直接把这重复的一段减去即可。即 \(\sum (n-sa(i)+1-height(i))\)。复杂度 \(\mathcal{O}(n\log n)\),瓶颈在于 SA。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,rk[200005],c[200005],sa[200005],tp[200005],ht[200005];char s[100005];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(){
	int m='z';
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=n;i>=n-w+1;i--)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	}
}
signed main(){
	n=read();scanf("%s",s+1);buildSA(); 
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==0)continue;
		if(k)--k;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		ans+=n-sa[i]+1-ht[i];
	}
	printf("%lld\n",ans);
	return 0;
}

可以算是 sam 的基本应用。建出 sam,答案即为 sam 上不同路径条数,拓扑一下即可。

J - 弦论

对于 \(t=0\) 很简单,直接 SA 就行,难点在于 \(t=1\)。如果我们直接模仿 \(t=0\) 的做法,找到第一个 \(\sum n-sa(i)+1>k\) 的位置的话,会发现这样求出来的答案会偏大,因为后面可能还有更小的前缀。考虑把此时这个串的真实排名表示出来,假设这是排名为 \(p\) 的后缀的长为 \(l\) 的前缀,那么 \(rank(p,l)=(\sum\limits_{i=1}^{p-1}n-sa(i)+1)+l+(\sum\limits_{i=p+1}^n\min(l,\min\limits_{j=p+1}^i\{height(j)\}))\)。容易发现一个串在 \(t=0\) 时的排名增加,它在 \(t=1\) 时的排名也会增加,于是就可以二分答案在 \(t=0\) 时的排名。复杂度 \(\mathcal{O}(n\log n+n\log k)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,rk[1000005],c[1000005],sa[1000005],tp[1000005],ht[1000005];char s[1000005];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(){
	int m='z';
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=n;i>=n-w+1;i--)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	}
}
int f[21][500005];
int ask(int l,int r){
	if(l>r)return inf;
	int o=__lg(r-l+1);
	return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int check(int mid,int k){
	int sum=0,pos=0,len=0;
	for(int i=1;i<=n;i++){
		if(sum+n-sa[i]+1-ht[i]>=mid){pos=i,len=mid-sum+ht[i];break;}
		sum+=n-sa[i]+1-ht[i];
	}
	if(pos==0)return 0;
	int cnt=len;
	for(int i=1;i<pos;i++)cnt+=n-sa[i]+1;
	for(int i=pos+1;i<=n;i++)cnt+=min(len,ask(pos+1,i));
	return (cnt>=k);
}
signed main(){
	scanf("%s",s+1);n=strlen(s+1);buildSA(); 
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==0)continue;
		if(k)--k;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	int t=read(),k=read();
	if(t==0){
		int sum=0;
		for(int i=1;i<=n;i++){
			if(sum+n-sa[i]+1-ht[i]>=k){
				for(int j=sa[i];j<=sa[i]+k-sum-1+ht[i];j++)printf("%c",s[j]);
				return 0;
			}
			sum+=n-sa[i]+1-ht[i];
		}
		puts("-1");
	}
	else{
		if(k>n*(n+1)/2)return puts("-1"),0;
		for(int i=1;i<=n;i++)f[0][i]=ht[i];
		for(int j=1;(1ll<<j)<=n;j++){
			for(int i=1;i+(1ll<<j)-1<=n;i++){
				f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
			}
		}
		int l=1,r=k,res=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(mid,k))res=mid,r=mid-1;
			else l=mid+1;
		}
		int sum=0;
		for(int i=1;i<=n;i++){
			if(sum+n-sa[i]+1-ht[i]>=res){
				for(int j=sa[i];j<=sa[i]+res-sum-1+ht[i];j++)printf("%c",s[j]);
				return 0;
			}
			sum+=n-sa[i]+1-ht[i];
		}
	}
	return 0;
}

K - 字符串

考虑二分答案 \(mid\),现在需要判断 \(\max\limits_{i=a}^{b-mid+1}\{\text{lcp}(s[i\ldots n],s[c\ldots d])\}\) 是否不小于 \(mid\)。考虑再次二分出满足条件的 \(i\) 的排名对应的区间,问题变成判断区间内是否存在一段区间内的数,主席树即可,复杂度 \(\mathcal{O}(n\log^2 n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,q,rk[200005],c[200005],sa[200005],tp[200005],ht[200005];char s[100005];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(){
	int m='z';
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=n;i>=n-w+1;i--)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	}
}
int root[100005];
struct segtree{
	#define ls (c[p].lc)
	#define rs (c[p].rc)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int lc,rc,s;
	}c[3000005];
	int T,root[100005];
	void pushup(int p){
		c[p].s=c[ls].s+c[rs].s;
	}
	int build(int l,int r){
		int p=++T;
		if(l==r){c[p].s=0;return p;}
		int mid=(l+r)>>1;
		ls=build(l,mid);rs=build(mid+1,r);
		pushup(p);return p;
	}
	int add(int l,int r,int q,int x,int k){
		int p=++T;c[p]=c[q];
		if(l==r){c[p].s+=k;return p;}
		int mid=(l+r)>>1;
		if(x<=mid)ls=add(l,mid,c[q].lc,x,k);
		else rs=add(mid+1,r,c[q].rc,x,k);
		pushup(p);return p;
	}
	int ask(int l,int r,int p,int q,int L,int R){
		if(L>R)return 0;
		if(L<=l&&r<=R){
			return c[q].s-c[p].s;
		}
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res+=ask(lson,c[q].lc,L,R);
		if(R>mid)res+=ask(rson,c[q].rc,L,R);
		return res;
	}
	#undef lson
	#undef rson
	#undef ls
	#undef rs
}Tr;
int f[20][100005];
int ask(int l,int r){
	if(l>r)return inf;
	int o=__lg(r-l+1);
	return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int check(int len,int a,int b,int c,int d){
	if(a<=c&&c<=b-len+1)return 1;
	int l=1,r=rk[c]-1,res=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(ask(mid+1,rk[c])>=len)res=mid,r=mid-1;
		else l=mid+1;
	}
	if(res!=-1&&Tr.ask(1,n,root[a-1],root[b-len+1],res,rk[c]-1))return 1;
	l=rk[c]+1,r=n,res=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(ask(rk[c]+1,mid)>=len)res=mid,l=mid+1;
		else r=mid-1;
	}
	if(res!=-1&&Tr.ask(1,n,root[a-1],root[b-len+1],rk[c]+1,res))return 1;
	return 0;
}
signed main(){
	n=read(),q=read();
	scanf("%s",s+1);buildSA();
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==1){ht[rk[i]]=0;continue;}
		if(k)k--;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	for(int i=1;i<=n;i++)f[0][i]=ht[i];
	for(int j=1;(1ll<<j)<=n;j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	root[0]=Tr.build(1,n);
	for(int i=1;i<=n;i++)root[i]=Tr.add(1,n,root[i-1],rk[i],1);
	while(q--){
		int a=read(),b=read(),c=read(),d=read();
		int l=1,r=min(b-a+1,d-c+1),res=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(mid,a,b,c,d))res=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%lld\n",res);
	}
	return 0;
}

L - 优秀的拆分

考虑怎么统计答案。统计每个位置 \(i\) 成为形如 AA 的开头/结尾的次数,记作 \(a_i,b_i\),那么答案就是 \(\sum b_ia_{i+1}\)。如果直接 \(\mathcal{O}(n^2)\) 枚举每个串然后 hash 判断的话可以得 95pts。

正解基于一个很智慧的结论。考虑枚举这些串的长度 \(2len\),注意到如果我们把所有编号为 \(len\) 的倍数的点打上标记的话,一个 AA 串上一定会有刚好两个标记。不妨在这两个标记点 \(i\)\(j\) 上统计这个串。可以枚举这个串在 \(i\) 及之前的长度 \(x\)\(j\) 之后的长度 \(y\),有 \(x+y=len\),且 \(1\le x\le\text{lcs}(s[1\ldots i],s[1\ldots j])\)\(0\le y\le\text{lcp}(s[i+1\ldots n],s[j+1\ldots n])\)。注意到合法的左端点只会是一段区间,后缀数组预处理后算一下即可。复杂度 \(\mathcal{O}(n\log n+n\ln n)\)

注意数组不要忘了清空,比如 \(tp\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,rk[120005],c[120005],sa[120005],tp[120005],ht[120005];char s[120005];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(){
	int m='z';
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=n;i>=n-w+1;i--)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	}
}
int f[20][60005],g[20][60005];
int askf(int l,int r){
	if(l>r)return 0;
	int o=__lg(r-l+1);
	return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int askg(int l,int r){
	if(l>r)return 0;
	int o=__lg(r-l+1);
	return min(g[o][l],g[o][r-(1ll<<o)+1]);
}
int rk1[120005],rk2[120005],a[120005],b[120005];
void solve(){
	scanf("%s",s+1);n=strlen(s+1);buildSA();
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==1){ht[rk[i]]=0;continue;}
		if(k)k--;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	for(int i=1;i<=n;i++)f[0][i]=ht[i];
	for(int j=1;(1ll<<j)<=n;j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1;i<=n;i++)rk1[i]=rk[i];
	reverse(s+1,s+n+1);buildSA();
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==1){ht[rk[i]]=0;continue;}
		if(k)k--;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	for(int i=1;i<=n;i++)g[0][i]=ht[i];
	for(int j=1;(1ll<<j)<=n;j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			g[j][i]=min(g[j-1][i],g[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1;i<=n;i++)rk2[i]=rk[i];
	for(int len=1;2*len<=n;len++){
		for(int i=len;i+len<=n;i+=len){
			int la=min(len,askg(min(rk2[n-i+1],rk2[n-(i+len)+1])+1,max(rk2[n-i+1],rk2[n-(i+len)+1])));
			int lb=min(len,askf(min(rk1[i+1],rk1[i+len+1])+1,max(rk1[i+1],rk1[i+len+1])));
			int L=max(1ll,len-lb),R=min(la,len);
			int l=(i+1)-R,r=(i+1)-L;
			if(l<=r)a[l]++,a[r+1]--,b[l+2*len-1]++,b[r+2*len]--;
		}
	}
	for(int i=1;i<=n;i++)a[i]+=a[i-1],b[i]+=b[i-1];
	int ans=0;for(int i=1;i+1<=n;i++)ans+=b[i]*a[i+1];
	printf("%lld\n",ans);
	for(int i=0;i<=2*n;i++)a[i]=b[i]=rk1[i]=rk2[i]=rk[i]=ht[i]=sa[i]=tp[i]=0;
}	
signed main(){
	int T=read();
	while(T--){
		solve();
	}
	return 0;
}

M - 字符串

智慧放缩。

这个条件看上去就不是很好统计的样子,考虑弱化它,找一个必要条件,然后减去不合法的情况。令 \(t=s+('z'+1)+rev(s)+('a'-1)\)\(m=|t|\),注意到一个二元组 \((i,k)\) 合法的必要条件是 \(t[i\ldots m]<t[m-i+1-2k\ldots m]\),这个可以后缀数组求一下。

\(r_i\) 表示最大的 \(l\),满足 \(s[i-l+1\ldots i]=s[i+1\ldots i+l]\)。注意到当 \(i+k-1=j\)\([i,i+2k-1]\subset [j-r_j+1,j+r_j]\)\(s_{j-r_j}<s_{j+r_j+1}\) 时这不是充分条件,这就等价于对每个询问 \((i,k)\),求 \(j-r_j+1\le i\),且 \(i\le j\le i+k-1\)\(j\) 的个数,扫一遍即可。

复杂度 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define ull unsigned long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
const ull base=13331;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int rk[400015],sa[400015],c[400015],tp[400015];char s[200015],t[200015];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(int n){
	int m='z'+1;
	for(int i=1;i<=n;i++)rk[i]=t[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=n;i>=n-w+1;i--)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	} 
}
int ht[200015];ull pw[200015],f[200015],g[200015];
ull askf(int l,int r){
	return f[r]-f[l-1]*pw[r-l+1];
}
ull askg(int l,int r){
	return g[l]-g[r+1]*pw[r-l+1];
}
struct BIT{
	int n,c[400015];
	void build(int _n){
		n=_n;
		for(int i=1;i<=n;i++)c[i]=0;
	}
	void add(int x,int v){
		for(;x<=n;x+=x&-x)c[x]+=v;
	}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res+=c[x];
		return res;
	}
	int ask(int l,int r){
		l=max(l,1ll),r=min(r,n);
		if(l>r)return 0;
		return ask(r)-ask(l-1);
	}
}Tr[2];
int qi[100005],qk[100005],ans[100005];vector<int>v[100005],w[100005];
void solve(){
	int n=read(),m=0,q=read();scanf("%s",s+1);
	for(int i=1;i<=n;i++)t[++m]=s[i];
	t[++m]='z'+1;
	for(int i=n;i>=1;i--)t[++m]=s[i];
	t[++m]='a'-1;
	buildSA(m);
	for(int i=1,k=0;i<=m;i++){
		if(rk[i]==1){ht[rk[i]]=0;continue;}
		if(k)k--;
		while(t[i+k]==t[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	pw[0]=1;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*base;
	f[0]=0;for(int i=1;i<=n;i++)f[i]=f[i-1]*base+s[i];
	g[n+1]=0;for(int i=n;i>=1;i--)g[i]=g[i+1]*base+s[i];
	for(int i=1;i<=q;i++)qi[i]=read(),qk[i]=read(),v[qi[i]].push_back(i);
	for(int i=1;i<=q;i++)qk[i]=min(qk[i],(n+1-qi[i])/2);
	for(int i=0;i<2;i++)Tr[i].build(m);
	for(int i=m;i>=1;i--){
		if(sa[i]<=n){
			for(auto x:v[sa[i]]){
				ans[x]=Tr[(sa[i]+1)&1ll].ask(m-sa[i]+1-2*qk[x],m-sa[i]+1-2);
			}
		}
		Tr[sa[i]&1ll].add(sa[i],1);
	}
	for(int i=1;i<=n;i++){
		int l=1,r=min(i,n-i),res=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(askf(i-mid+1,i)==askg(i+1,i+mid))res=mid,l=mid+1;
			else r=mid-1;
		}
		if(!res||i-res+1==1||i+res==n||s[i-res]<s[i+res+1])continue;
		w[i-res+1].push_back(i);
	}
	Tr[0].build(m);
	for(int i=1;i<=n;i++){
		for(auto x:w[i])Tr[0].add(x,1);
		for(auto x:v[i])ans[x]-=Tr[0].ask(qi[x],qi[x]+qk[x]-1);
	}
	for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
	for(int i=0;i<=2*m;i++)sa[i]=rk[i]=tp[i]=c[i]=0;
	for(int i=0;i<=n;i++)s[i]=0,v[i].clear(),w[i].clear();
	for(int i=0;i<=m;i++)t[i]=ht[i]=0;
}
signed main(){
	int C=read(),T=read();
	while(T--){
		solve();
	}
	return 0;
}

N - 事情的相似度

这是一个 SA + 回滚莫队 + 分块的 \(n\sqrt{n}\) 做法。

把整个串 reverse 一下,问题就是结束点在 \([l,r]\) 范围内的任意两个后缀的 lcp 的 max。不难发现 max 只会在区间内排名相邻的两个后缀处取到。考虑只删的回滚莫队,链表维护前驱后继。考虑怎么统计答案。注意到修改次数为 \(\mathcal{O}(n\sqrt{n})\) 而只会询问 \(\mathcal{O}(n)\) 次,且答案不超过 \(n\)。考虑把所有备选答案丢到值域分块上,复杂度 \(\mathcal{O}(n\log n+n\sqrt{n})\)

SAM 做法学会了 lct 再补。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,Q,siz,num,rk[1000005],c[1000005],sa[1000005],tp[1000005],ht[1000005];char s[1000005];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(){
	int m='1';
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=n;i>=n-w+1;i--)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=1;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	}
}
struct Que{
	int l,r,id;
}q[100005];
int bel[100005],L[505],R[505];
int Cmp(Que x,Que y){
	if(bel[x.l]!=bel[y.l])return bel[x.l]<bel[y.l];
	else return x.r>y.r;
}
int f[20][100005];
int ask(int l,int r){
	assert(l<=r);
	if(l>r)return inf;
	int o=__lg(r-l+1);
	return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int B[100005],cnt[100005],pre[100005],nxt[100005],s1[100005],s2[505],ans[100005];
void Add(int x,int v){
	s1[x]+=v,s2[bel[x]]+=v;
}
int Ask(){
	for(int i=num;i>=1;i--){
		if(s2[i]){
			for(int j=R[i];j>=L[i];j--)if(s1[j])return j;
			return 0;
		}
	}
	return 0;
}
void del(int x){
	if(pre[x])Add(ask(pre[x]+1,x),-1);
	if(nxt[x])Add(ask(x+1,nxt[x]),-1);
	if(pre[x]&&nxt[x])Add(ask(pre[x]+1,nxt[x]),1);
	pre[nxt[x]]=pre[x],nxt[pre[x]]=nxt[x]; 
}
struct Node{
	int a,b,c;
};
signed main(){
	n=read(),Q=read(),siz=(int)sqrt(n),num=(n+siz-1)/siz;
	for(int i=1;i<=n;i++)bel[i]=(i-1)/siz+1;
	for(int i=1;i<=num;i++)L[i]=(i-1)*siz+1,R[i]=min(n,i*siz);
	scanf("%s",s+1);reverse(s+1,s+n+1);buildSA();
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==1){ht[rk[i]]=0;continue;}
		if(k)k--;
		while(s[i+k]==s[sa[rk[i]-1]+k])k++;
		ht[rk[i]]=k;
	}
	for(int i=1;i<=n;i++)f[0][i]=ht[i];
	for(int j=1;(1ll<<j)<=n;j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1,l,r;i<=Q;i++){
		l=read(),r=read(),q[i]=(Que){n-r+1,n-l+1,i};
	}
	sort(q+1,q+Q+1,Cmp);
	for(int j=1,i=1;j<=num;j++){
		int l=L[j],r=n,lst=0;
		for(int k=0;k<=n;k++)B[k]=cnt[k]=pre[k]=nxt[k]=0;
		for(int k=1;k<=n;k++)s1[k]=0;
		for(int k=1;k<=num;k++)s2[k]=0;
		for(int k=L[j];k<=n;k++)cnt[rk[k]]++;
		for(int k=1;k<=n;k++)if(cnt[k])pre[k]=lst,nxt[lst]=k,lst=k;
		for(int k=1;k<=n;k++)if(cnt[k]&&pre[k])Add(ask(pre[k]+1,k),1);
		while(i<=Q){
			int ql=q[i].l,qr=q[i].r,id=q[i].id;
			if(bel[ql]!=j)break;
			while(r>qr)del(rk[r--]);
			stack<Node>s;
			while(l<ql)s.push((Node){rk[l],pre[rk[l]],nxt[rk[l]]}),del(rk[l++]);
			ans[id]=Ask();
			l=L[j];
			while(!s.empty()){
				Node u=s.top();s.pop();
				if(u.b&&u.c)Add(ask(u.b+1,u.c),-1);
				if(u.b)Add(ask(u.b+1,u.a),1);
				if(u.c)Add(ask(u.a+1,u.c),1);
				nxt[u.b]=u.a,pre[u.a]=u.b,pre[u.c]=u.a,nxt[u.a]=u.c;
			}
			i++;
		}
	}
	for(int i=1;i<=Q;i++)printf("%lld\n",ans[i]);
	return 0;
}

O - Simple KMP

考虑把贡献写得形式化一点。

\[key(s)=\sum\limits_{l=1}^n\sum\limits_{r=l}^n\sum\limits_{i=l}^r\sum\limits_{k=1}^{i-l}[s[l\ldots l+k-1]=s[i-k+1\ldots i]] \]

不妨记一个 \(f(i)=\sum\limits_{l=1}^i\sum\limits_{k=1}^{i-l}[s[l\ldots l+k-1]=s[i-k+1\ldots i]]\)。那么 \(key(s)=\sum\limits_{r=1}^n\sum\limits_{i=1}^r f(i)\)。考虑求 \(f(i)\)。当然我们不可能在线做。考虑对整个 \(s\) 建 sam,枚举 \(i=1\sim n\),记 \(s[1\ldots i]\) 在 parent 树上对应的点为 \(x_i\),只需要对 \(x_i\)\(1\) 路径 \(cnt\) 加一,\(x_i\) 到根求和,即可得到 \(f(i)\),对 \(f(i)\) 做两遍前缀和就是答案。大力树剖即可维护,复杂度 \(\mathcal{O}(n\log^2 n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct SAM{
	int tot=1,lst=1,ch[200005][28],link[200005],len[200005];
	vector<int>g[200005];
	void add(int c){
		int y=lst,x=++tot;lst=x,len[x]=len[y]+1;
		while(y&&!ch[y][c])ch[y][c]=x,y=link[y];
		if(!y){link[x]=1;return;}
		int p=y,q=ch[p][c];
		if(len[q]==len[p]+1){link[x]=q;return;}
		int Q=++tot;len[Q]=len[p]+1;
		link[Q]=link[q],link[q]=link[x]=Q;
		for(int i=0;i<26;i++)ch[Q][i]=ch[q][i];
		while(y&&ch[y][c]==q)ch[y][c]=Q,y=link[y]; 
	}
	void build(){
		for(int i=2;i<=tot;i++)g[link[i]].push_back(i);
	}
}sam;
int s[200005];
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int s,tag;
	}c[800005];
	void pushup(int p){
		c[p].s=(c[ls].s+c[rs].s)%mod; 
	}
	void pushdown(int l,int r,int p){
		if(!c[p].tag)return;
		int mid=(l+r)>>1;
		c[ls].s=(c[ls].s+(s[mid]-s[l-1]+mod)%mod*c[p].tag%mod)%mod;
		c[rs].s=(c[rs].s+(s[r]-s[mid]+mod)%mod*c[p].tag%mod)%mod;
		c[ls].tag=(c[ls].tag+c[p].tag)%mod,c[rs].tag=(c[rs].tag+c[p].tag)%mod;
		c[p].tag=0;  
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){c[p].s=0;return;}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void add(int l,int r,int p,int L,int R,int k){
		if(L<=l&&r<=R){c[p].s=(c[p].s+(s[r]-s[l-1]+mod)%mod*k%mod)%mod,c[p].tag=(c[p].tag+k)%mod;return;}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)add(lson,L,R,k);
		if(R>mid)add(rson,L,R,k);
		pushup(p);
	}
	int qry(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p].s;
		int mid=(l+r)>>1,res=0;pushdown(l,r,p);
		if(L<=mid)res=(res+qry(lson,L,R))%mod;
		if(R>mid)res=(res+qry(rson,L,R))%mod;
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
int siz[200005],son[200005],dep[200005];
void dfs1(int u){
	siz[u]=1,son[u]=0;
	for(auto v:sam.g[u]){
		dep[v]=dep[u]+1,dfs1(v),siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
int cur,dfn[200005],rnk[200005],top[200005];
void dfs2(int u,int rt){
	dfn[u]=++cur,rnk[cur]=u,top[u]=rt;
	if(son[u])dfs2(son[u],rt);
	for(auto v:sam.g[u]){
		if(v!=son[u])dfs2(v,v);
	}
}
void addPath(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		Tr.add(1,cur,1,dfn[top[u]],dfn[u],k);
		u=sam.link[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	Tr.add(1,cur,1,dfn[v],dfn[u],k);
}
int askPath(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res=(res+Tr.qry(1,cur,1,dfn[top[u]],dfn[u]))%mod;
		u=sam.link[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	res=(res+Tr.qry(1,cur,1,dfn[v],dfn[u]))%mod;
	return res;
}
int ans[100005];char t[100005];
signed main(){
	int n=read();scanf("%s",t+1);
	for(int i=1;i<=n;i++)sam.add(t[i]-'a');
	sam.build();dfs1(1);dfs2(1,1);
	for(int i=1;i<=cur;i++)s[i]=(s[i-1]+sam.len[rnk[i]]-sam.len[sam.link[rnk[i]]])%mod;
	Tr.build(1,cur,1);
	for(int i=1,u=1;i<=n;i++){
		u=sam.ch[u][t[i]-'a'];ans[i]=(ans[i-1]+askPath(u,1))%mod;addPath(u,1,1);
	}
	for(int i=1;i<=n;i++)ans[i]=(ans[i]+ans[i-1])%mod;
	for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
	return 0;
}

P - Substrings in a String

一个 bitset 做法。

考虑优化暴力匹配的过程。对文本串 \(s\) 中每种字符开一个 bitset \(B_c\) 表示字符 c 的所有出现位置。对于求文本串 \(t\)\([l,r]\) 中每次出现的出现位置,定义 \(len\)\(t\) 的长度,我们有以下套路:定义 bitset \(X\)\(X\) 中第 \(i\) 位为 1 表示 \([i,i+len-1]\)\(t\)。遍历 \(t\) 的每一位,同时 \(X\leftarrow X\And B_{t_i}\),最后 \(X\) 中为 1 的位置即为所求。本题中我们统计的是 \(X\)\([l,r-len+1]\) 的答案。注意特判 \(len>r-l+1\) 的情况。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int id(char ch){
	if('a'<=ch&&ch<='z')return ch-'a';
	if('A'<=ch&&ch<='Z')return ch-'A'+26;
	if('0'<=ch&&ch<='9')return ch-'0'+52;
	return ch-'_'+62;
}
char s[100005],t[100005];bitset<100005>B[64],ans;
int ask(bitset<100005>a,int l,int r,int len){
	return (a>>l).count()-(a>>(r-len+2)).count();
}
signed main(){
	scanf("%s",s+1);int n=strlen(s+1),m=read();for(int i=1;i<=n;i++)B[id(s[i])].flip(i);
	while(m--){
		int op=read(),l,r;
		if(op==1){l=r=read();scanf("%s",t+1);for(int i=l;i<=r;i++)B[id(s[i])].flip(i),s[i]=t[i-l+1],B[id(s[i])].flip(i);}
		else{l=read(),r=read();scanf("%s",t+1);int len=strlen(t+1);if(len>r-l+1){puts("0");continue;}ans.set();for(int i=1;i<=len;i++)ans&=(B[id(t[i])]>>(i-1));printf("%d\n",ask(ans,l,r,len));}
	}
	return 0;
}

一个正经的分块 sam 做法。下文假定 \(|s|=n\)\(q\)\(\sum|y|\) 同阶。

考虑每次询问直接 kmp 暴力匹配,复杂度 \(\mathcal{O}(n^2)\)。注意到这样很慢的原因在于 kmp 匹配的复杂度是 \(\mathcal{O}(n+m)\),考虑一些能统计出现次数,且复杂度与 \(n\) 无关的东西,比如 sam。注意到还需要维护修改操作,还有这个 \(10^5\) 的看起来就很根号的数据范围,考虑分块,对每个长为 \(T\) 的块建一个 sam,每次修改时暴力重构。复杂度 \(\mathcal{O}(nT)\)

思考现在怎么匹配。对于 \(|y|>T\),这样的询问只会出现不超过 \(\dfrac{\sum|y|}{T}\) 次,可以直接暴力建 sam 然后匹配,复杂度 \(\mathcal{O}(\dfrac{n^2}{T})\)。否则 \(y\) 要么在一个块里,要么在两个块中间,在块里可以直接用维护的 sam 匹配,边角块可以暴力建 sam,复杂度 \(\mathcal{O}(\dfrac{n^2}{T}+nT)\)。另一种情况可以枚举 \(y\) 在哪两个块中间,算出可能的范围后建 sam。注意到这个区间长度不超过 \(2|y|\),复杂度 \(\mathcal{O}(\dfrac{n^2}{T})\)

\(T=\sqrt{n}\) 可以得到复杂度 \(\mathcal{O}(n\sqrt{n})\)。看上去很快?实际上因为建 sam 的复杂度是 \(\mathcal{O}(|\sum|n)\) 而不是 \(\mathcal{O}(n)\),还需要带一个 26 的常数,需要大力卡常。比如刚刚那些暴力建 sam 的地方可以用 kmp 少掉一个 26 的常数,只有块内需要用 sam。如果还不能过的话可以调下块长。重构记得清空 parent 树,不然会 mle。

跑得很慢,被 bitset 做法爆踩。

点击查看代码
#include<bits/stdc++.h>
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct SAM{
	int tot=1,lst=1,ch[705][28],link[705],len[705],siz[705];vector<int>g[705];
	void clear(){
		for(int i=1;i<=tot;i++){
			for(int j=0;j<26;j++)ch[i][j]=0;
			link[i]=len[i]=siz[i]=0;
			vector<int>().swap(g[i]);
		}
		tot=lst=1;
	}
	void add(int c){
		int y=lst,x=++tot;lst=x;len[x]=len[y]+1,siz[x]=1;
		while(y&&!ch[y][c])ch[y][c]=x,y=link[y];
		if(!y){link[x]=1;return;}
		int p=y,q=ch[p][c];
		if(len[q]==len[p]+1){link[x]=q;return;}
		int Q=++tot;link[Q]=link[q],link[q]=link[x]=Q;len[Q]=len[p]+1;
		for(int i=0;i<26;i++)ch[Q][i]=ch[q][i];
		while(y&&ch[y][c]==q)ch[y][c]=Q,y=link[y];
	}
	void build(){
		for(int i=2;i<=tot;i++)g[link[i]].push_back(i);
	}
	void dfs(int u){
		for(auto v:g[u])dfs(v),siz[u]+=siz[v];
	}
	int calc(char *t,int len){
		int u=1;
		for(int i=1;i<=len;i++){
			if(!ch[u][t[i]-'a'])return 0;
			u=ch[u][t[i]-'a'];
		}
		return siz[u];
	}
}sam[355];
int bel[100005],L[355],R[355],nxt[100005];char s[100005],t[100005];
void initnxt(int len){
	nxt[1]=0;
	for(int i=2,j=0;i<=len;i++){
		while(j&&t[j+1]!=t[i])j=nxt[j];
		if(t[j+1]==t[i])j++;
		nxt[i]=j;
	}
}
int count(int l,int r,int len){
	int ans=0;
	for(int i=l,j=0;i<=r;i++){
		while(j&&t[j+1]!=s[i])j=nxt[j];
		if(t[j+1]==s[i])j++;
		if(j==len)ans++,j=nxt[j];
	}
	return ans;
}
signed main(){
	scanf("%s",s+1);
	int n=strlen(s+1),siz=(int)sqrt(n),num=(n+siz-1)/siz;
	for(int i=1;i<=n;i++)bel[i]=(i-1)/siz+1;
	for(int i=1;i<=num;i++)L[i]=(i-1)*siz+1,R[i]=min(i*siz,n);
	for(int i=1;i<=num;i++){
		sam[i].clear();
		for(int j=L[i];j<=R[i];j++)sam[i].add(s[j]-'a');
		sam[i].build();sam[i].dfs(1);
	}
	int q=read();
	while(q--){
		int op=read();
		if(op==1){
			int i=read();scanf("%s",t+1);s[i]=t[1];
			i=bel[i];sam[i].clear();
			for(int j=L[i];j<=R[i];j++)sam[i].add(s[j]-'a');
			sam[i].build();sam[i].dfs(1);		
		}
		else{
			int l=read(),r=read();scanf("%s",t+1);
			int len=strlen(t+1),ans=0;
			if(len>r-l+1){puts("0");continue;}
			initnxt(len);
			if(len<=siz){
				if(bel[l]==bel[r])ans+=count(l,r,len);
				else{
					ans+=count(l,R[bel[l]],len);
					ans+=count(L[bel[r]],r,len);
					for(int i=bel[l]+1;i<=bel[r]-1;i++){
						if(R[i]-L[i]+1<len)continue;
						ans+=sam[i].calc(t,len);
					}
					for(int i=bel[l];i<=bel[r]-1;i++){
						int lp=max(1,len-r-1+L[i+1]),rp=min(len-1,R[i]+1-l);
						if(lp<=rp)ans+=count(R[i]+1-rp,(R[i]+1-lp)+len-1,len);
					}			
				}
			}
			else ans+=count(l,r,len);
			printf("%d\n",ans);
		}
	}
	return 0;
}

R - 封印

一个 SA 做法!

考虑把 \(t\) 拼到 \(s\) 后面,中间随便插一个什么别的字符。先对这个串跑个 SA,假设当前是问 \([l,r]\),考虑二分答案 mid,需要判定 \(\max\text{lcp}(s[i\ldots |s|+|t|+1],s[j+|s|+1\ldots |s|+|t|+1])\) 是否 \(\ge mid\),其中 \(i\in[i,r-mid+1]\)\(j\in[|s|+2,|s|+|t|+2-mid]\)。注意到 \(j\) 的范围限制是没意义的,可以直接取满 \(t\) 对应的范围。然后因为 \(j\) 的范围已经可以取满了,需要求 \(\max \text{lcp}\),那么一个 \(i\) 一定是和它左右两边最近的 \(j\) 匹配,可以 ST 表预处理,复杂度 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,sa[8000005],rk[8000005],c[4000005],tp[8000005],ht[4000005];char s[4000005],t[2000005];
int cmp(int x,int y,int w){
	return (tp[x]==tp[y]&&tp[x+w]==tp[y+w]);
}
void buildSA(){
	m=max('z','#');
	for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
	for(int i=0;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[rk[i]]++;
	for(int i=0;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
	for(int w=1,p=0;;w<<=1){
		for(int i=1;i<=w;i++)tp[++p]=n-w+i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		for(int i=0;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[rk[i]]++;
		for(int i=0;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[rk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rk[i];
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(cmp(sa[i-1],sa[i],w)?p:++p);
		if(p==n)break;
		m=p,p=0;
	}
	for(int i=1,k=0;i<=n;i++){
		if(rk[i]==1){ht[rk[i]]=0;continue;}
		if(k)--k;
		int j=sa[rk[i]-1];
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
		ht[rk[i]]=k;
	}
}
int Log[8000005],f[2000005][25],g[4000005][25];
int askF(int l,int r){
	int o=Log[r-l+1];
	return max(f[l][o],f[r-(1ll<<o)+1][o]);
}
int askG(int l,int r){
	int o=Log[r-l+1];
	return min(g[l][o],g[r-(1ll<<o)+1][o]);
}
signed main(){
	scanf("%s%s",s+1,t+1);
	int N=strlen(s+1),M=strlen(t+1);
	s[++N]='#',n=N;
	for(int i=1;i<=M;i++)s[i+n]=t[i];
	n+=M;buildSA();
	Log[1]=0;
	for(int i=2;i<=N;i++)Log[i]=Log[i>>1]+1;
	for(int i=1;i<=N+M;i++)g[i][0]=ht[i];
	for(int j=1;(1ll<<j)<=N+M;j++){
		for(int i=1;i+(1ll<<j)-1<=N+M;i++){
			g[i][j]=min(g[i][j-1],g[i+(1ll<<(j-1))][j-1]);
		}
	}
	int lst=0;
	for(int i=1;i<=N;i++)f[i][0]=-inf;
	for(int i=1;i<=N+M;i++){
		if(sa[i]>N)lst=i;
		else if(lst)f[sa[i]][0]=max(f[sa[i]][0],askG(lst+1,i));	
	}
	lst=0;
	for(int i=N+M;i>=1;i--){
		if(sa[i]>N)lst=i;
		else if(lst)f[sa[i]][0]=max(f[sa[i]][0],askG(i+1,lst));	
	}	
	for(int j=1;(1ll<<j)<=N;j++){
		for(int i=1;i+(1ll<<j)-1<=N;i++){
			f[i][j]=max(f[i][j-1],f[i+(1ll<<(j-1))][j-1]);
		}
	}
	int q=read();
	while(q--){
		int L=read(),R=read();
		int l=0,r=R-L+1,res=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(askF(L,R-mid+1)>=mid)res=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%lld\n",res);
	}
	return 0;
}

一个 sam 做法。

\(s[l\ldots r]\)\(t\) 的最长公共子串,不妨二分答案 \(L\),即求 \(\exists i\in[l,r-L+1]\)\(s[1\ldots i]\)\(t\) 的最长公共子串长度 \(\ge L\)

套路的,考虑对 \(t\) 建 sam,预处理 \(s\) 的每个前缀与 \(t\) 的最长公共子串的长度。考虑从小到大处理。对于 \(s[1\ldots i]\),这就相当于在 sam 上找一条最长的路径,满足它是这个前缀的一段后缀。记处理到 \(i-1\) 后的当前节点为 \(u\) 和当前答案为 \(t\),如果 \(trans(u,s_i)\) 存在那么直接 \(u\gets trans(u,s_i)\)\(t\gets t+1\)。否则,我们需要不断截取一段后缀,即 \(u\gets link(u)\)\(t\gets len(u)\),直到它存在转移。

注意此时不能简单的令答案为 \(len(u)\),因为我们只知道答案在 \(u\) 这个节点对应的字符串集合里,所以 \(len(u)\) 不一定是答案。但是在跳 \(link\) 时答案一定就是 \(len\) 了。

复杂度显然 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct SAM{
	int tot=1,lst=1,ch[400005][28],link[400005],len[400005];
	void add(int c){
		int y=lst,x=++tot;lst=x;len[x]=len[y]+1;
		while(y&&!ch[y][c])ch[y][c]=x,y=link[y];
		if(!y){link[x]=1;return;}
		int p=y,q=ch[p][c];
		if(len[q]==len[p]+1){link[x]=q;return;}
		int Q=++tot;len[Q]=len[p]+1;
		link[Q]=link[q],link[q]=link[x]=Q;
		for(int i=0;i<26;i++)ch[Q][i]=ch[q][i];
		while(y&&ch[y][c]==q)ch[y][c]=Q,y=link[y];
	}
}sam;
int f[20][200005];
int ask(int l,int r){
	assert(l<=r);
	int o=__lg(r-l+1);
	return max(f[o][l],f[o][r-(1ll<<o)+1]);
}
char s[200005],t[200005];
signed main(){
	scanf("%s%s",s+1,t+1);
	int n=strlen(s+1),m=strlen(t+1),q=read();
	for(int i=1;i<=m;i++)sam.add(t[i]-'a');
	for(int i=1,u=1,l=0;i<=n;i++){
		while(u&&!sam.ch[u][s[i]-'a'])u=sam.link[u],l=sam.len[u];
		if(u)u=sam.ch[u][s[i]-'a'],l++;
		else u=1,l=0;
		f[0][i]=l;
	}
	for(int j=1;(1ll<<j)<=n;j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=max(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	while(q--){
		int a=read(),b=read();
		int l=1,r=b-a+1,res=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(ask(a+mid-1,b)>=mid)res=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%lld\n",res);
	}
	return 0;
}

Q - 匹配

我什么时候写过这种东西???

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18,V=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct SAM{
	int tot=1,lst=1,ch[200005][12],link[200005],len[200005];
	vector<int>g[200005];
	void clear(){
		for(int i=1;i<=tot;i++){
			link[i]=len[i]=0,g[i].clear();
			for(int j=0;j<10;j++)ch[i][j]=0;
			g[i].shrink_to_fit();
		}
		tot=lst=1;
	}
	void add(int c){
		int y=lst,x=++tot;lst=x,len[x]=len[y]+1;
		while(y&&!ch[y][c])ch[y][c]=x,y=link[y];
		if(!y){link[x]=1;return;}
		int p=y,q=ch[p][c];
		if(len[q]==len[p]+1){link[x]=q;return;}
		int Q=++tot;len[Q]=len[p]+1;
		link[Q]=link[q],link[q]=link[x]=Q;
		for(int i=0;i<10;i++)ch[Q][i]=ch[q][i];
		while(y&&ch[y][c]==q)ch[y][c]=Q,y=link[y];
	}
	void build(){
		for(int i=2;i<=tot;i++)g[link[i]].push_back(i);
	}
}sam;
int pos[200005];
void dfs1(int u){
	for(auto v:sam.g[u]){
		dfs1(v);pos[u]=min(pos[u],pos[v]);
	}
}
int cur,siz[200005],dfn[200005];
void dfs2(int u){
	siz[u]=1,dfn[u]=++cur;
	for(auto v:sam.g[u]){
		dfs2(v);siz[u]+=siz[v];
	}
}
struct BIT{
	int c[200005];
	void add(int x,int v){
		for(;x<=cur;x+=x&-x)c[x]+=v;
	}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res+=c[x];
		return res;
	}
	int ask(int l,int r){
		return ask(r)-ask(l-1);
	}
}Tr;
int ans[50005];char s[100005];string t[50005];vector<pair<int,int> >q[100005];
signed main(){
	int n=read();scanf("%s",s+1);
	for(int i=1;i<=n;i++)sam.add(s[i]-'0');
	int m=read();sam.build();
	for(int i=1;i<=sam.tot;i++)pos[i]=inf;
	for(int i=1,u=1;i<=n;i++){
		u=sam.ch[u][s[i]-'0'],pos[u]=min(pos[u],i);
	}
	dfs1(1);dfs2(1);
	for(int i=1;i<=m;i++){
		cin>>t[i];int len=(int)t[i].size(),u=1;
		for(int j=0;j<len;j++){
			u=sam.ch[u][t[i][j]-'0'];
			if(!u)break;
		}
		if(!u){
			for(int j=0,v=1;j<len;j++){
				v=sam.ch[v][t[i][j]-'0'];
				if(!v)break;
				q[n+1].push_back({i,v});
			}
			ans[i]+=n;
		}
		else{
			assert(pos[u]<=n);
			for(int j=0,v=1;j<len;j++){
				v=sam.ch[v][t[i][j]-'0'];
				q[pos[u]-len+1+j].push_back({i,v});
			}
			ans[i]+=pos[u];
		}
	}
	for(int i=1,u=1;i<=n+1;i++){
		for(auto x:q[i]){
			ans[x.first]+=Tr.ask(dfn[x.second],dfn[x.second]+siz[x.second]-1);
//			msg("{%lld,%lld,%lld}{%lld}\n",i,x.first,x.second,Tr.ask(dfn[x.second],dfn[x.second]+siz[x.second]-1));
		}
		if(i<=n)u=sam.ch[u][s[i]-'0'],Tr.add(dfn[u],1);
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return 0;
}
/*
7
1090901
1
0901
*/

U - Palindrome Addicts

坏了,以前没写题解吗,补一下。

点击查看代码
#include<bits/stdc++.h>
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int l=1,r=0;char s[1000005];
struct PAM{
	//odd->1 even->0
	int lst=0,tot=1,num=0,ch[2000005][28],fail[2000005],len[2000005],tag[2000005],g[2000005];
	vector<int>t[2000005];
	void clear(){
		for(int i=0;i<=tot;i++){
			fail[i]=len[i]=tag[i]=g[i]=0;
			for(int j=0;j<26;j++)ch[i][j]=0;
		}
		lst=0,tot=1,num=0,fail[0]=1,fail[1]=1,len[0]=0,len[1]=-1;
	}
	void upd(int p,int k){
		if(p<2||!k)return;
		if(tag[p]==0)num++,g[fail[p]]++;
		if(tag[p]<k)tag[p]=k,t[k-len[p]+1].push_back(p);
	}
	int getfail(int x,int i){
		while(i-len[x]-1<0||s[i-len[x]-1]!=s[i]){
			msg("{%d,%d}{%d,%d}\n",x,i,fail[x],tag[x]),upd(fail[x],tag[x]),x=fail[x];
		}
		return x;
	}
	void add(int i){
		int p=getfail(lst,i);
		if(!ch[p][s[i]-'a']){
			int q=++tot;len[q]=len[p]+2;
			fail[q]=ch[getfail(fail[p],i)][s[i]-'a'],ch[p][s[i]-'a']=q;
		}
		lst=ch[p][s[i]-'a'];
		while(len[lst]>r-l+1)lst=fail[lst];
		upd(lst,i);
	}
	void del(int i){
		for(int j=0;j<(int)t[i].size();j++){
			int x=t[i][j];
			if(!g[x]&&tag[x]-len[x]+1==i)num--,g[fail[x]]--,upd(fail[x],tag[x]),tag[x]=0;
		}
	}
}pam;
char op[15];
signed main(){
	int q=read();pam.clear();
	while(q--){
		scanf("%s",op);
		if(op[1]=='u')scanf("%s",op),s[++r]=op[0],pam.add(r);
		else pam.del(l),l++;
		printf("%d\n",pam.num);
	} 
	return 0;
}
posted @ 2023-12-06 19:06  xx019  阅读(19)  评论(0编辑  收藏  举报