海亮寄 7.21

前言

业精于勤荒于嬉,行成于思毁于随

正文(加餐)

字符串选讲,这是要 \(DAY^{-1}\) 的节奏

浅贴一个课件

关键词:匹配、哈希、字典树未婚前后缀、前置转化

再浅贴一下题单

T1

我听到了 Z 函数的回响

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e7+5;
string a,b;int m,n,z[N],p[N];
inline void Z(){
	z[1]=n;int l=0,r=0;
	for(int i=2;i<=n;i++){
		if(i<=r)z[i]=min(z[i-l+1],r-i+1);
		while(i+z[i]<=n&&b[i+z[i]]==b[z[i]+1])z[i]++;
		if(i+z[i]-1>r)l=i,r=i+z[i]-1;
	}
	return;
}
inline void exkmp(){
	int l=0,r=0;
	for(int i=1;i<=m;i++){
		if(i<=r)p[i]=min(z[i-l+1],r-i+1);
		while(i+p[i]<=m&&a[i+p[i]]==b[p[i]+1])p[i]++;
		if(i+p[i]-1>r)l=i,r=i+p[i]-1;
	}
	return;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>a>>b;m=a.length(),n=b.length();a=' '+a,b=' '+b;
	Z();exkmp();
	int ans1=z[1]+1,ans2=p[1]+1;
	for(int i=2;i<=n;i++)ans1^=(i*(z[i]+1));
	for(int i=2;i<=m;i++)ans2^=(i*(p[i]+1));
	cout<<ans1<<endl<<ans2<<endl;
	return 0;
}

T2

热身题,循环移位(环)直接倍长(破环为链)

剩下的哈希或者 KMP 或者 EXKMP 都可以搞定!

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
string s1,s2;int m,n,z[N],p[N];
inline void Z(){
	z[1]=n;int l=0,r=0;
	for(int i=2;i<=n;i++){
		if(i<=r)z[i]=min(z[i-l+1],r-i+1);
		while(i+z[i]<=n&&s2[i+z[i]]==s2[z[i]+1])z[i]++;
		if(i+z[i]-1>r)l=i,r=i+z[i]-1;
	}
	return;
}
inline void exkmp(){
	int l=0,r=0;
	for(int i=1;i<=m;i++){
		if(i<=r)p[i]=min(z[i-l+1],r-i+1);
		while(i+p[i]<=m&&s1[i+p[i]]==s2[p[i]+1])p[i]++;
		if(i+p[i]-1>r)l=i,r=i+p[i]-1;
	}
	return;
}
inline void solve(){
	if(s1.length()<s2.length()){cout<<"no\n";return;}
	s1=' '+s1+s1;s2=' '+s2;
	m=s1.length()-1,n=s2.length()-1;
	Z();exkmp();
	// for(int i=1;i<=n;i++)cerr<<z[i]<<' ';
	// cerr<<endl;
	// for(int i=1;i<=m;i++)cerr<<p[i]<<' ';
	// cerr<<endl;
	// cerr<<"---------------"<<endl;
	for(int i=1;i<=m;i++)
		if(p[i]>=n){cout<<"yes\n";return;}
	cout<<"no\n";
	return;
}
inline void clr(){
	for(int i=1;i<=n;i++)z[i]=0;
	for(int i=1;i<=m;i++)p[i]=0;
	return;
}
signed main(){
	// freopen("in.txt","r",stdin);
	// freopen("out.txt","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	while(cin>>s1>>s2)solve(),clr();
	return 0;
}

T3

(哎,不会真的有人听 CDX 讲的写字典树吧?)

注意到两个回文串拼起来还是回文串可以哈希值的形式来表示

具体来说,记回文串 \(s\) 的哈希值为 \(a\),回文串 \(t\) 的哈希值为 \(b\),有:

\[a \times B^{|t|} + b = b \times B^{|s|} +a \]

其中 \(B\) 表示哈希对应的 base

移项,观测一轮,有:

\[\frac{a}{B^{|s|}-1} = \frac{b}{B^{|t|}-1} \]

于是乎,对于每一个串,直接维护上述式子,然后丢到 map

答案随便维护即可

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
string s1,s2;int m,n,z[N],p[N];
inline void Z(){
	z[1]=n;int l=0,r=0;
	for(int i=2;i<=n;i++){
		if(i<=r)z[i]=min(z[i-l+1],r-i+1);
		while(i+z[i]<=n&&s2[i+z[i]]==s2[z[i]+1])z[i]++;
		if(i+z[i]-1>r)l=i,r=i+z[i]-1;
	}
	return;
}
inline void exkmp(){
	int l=0,r=0;
	for(int i=1;i<=m;i++){
		if(i<=r)p[i]=min(z[i-l+1],r-i+1);
		while(i+p[i]<=m&&s1[i+p[i]]==s2[p[i]+1])p[i]++;
		if(i+p[i]-1>r)l=i,r=i+p[i]-1;
	}
	return;
}
inline void solve(){
	if(s1.length()<s2.length()){cout<<"no\n";return;}
	s1=' '+s1+s1;s2=' '+s2;
	m=s1.length()-1,n=s2.length()-1;
	Z();exkmp();
	// for(int i=1;i<=n;i++)cerr<<z[i]<<' ';
	// cerr<<endl;
	// for(int i=1;i<=m;i++)cerr<<p[i]<<' ';
	// cerr<<endl;
	// cerr<<"---------------"<<endl;
	for(int i=1;i<=m;i++)
		if(p[i]>=n){cout<<"yes\n";return;}
	cout<<"no\n";
	return;
}
inline void clr(){
	for(int i=1;i<=n;i++)z[i]=0;
	for(int i=1;i<=m;i++)p[i]=0;
	return;
}
signed main(){
	// freopen("in.txt","r",stdin);
	// freopen("out.txt","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	while(cin>>s1>>s2)solve(),clr();
	return 0;
}

T4

来自高效进阶的压迫感(据说可以点分治?)

点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=1e5+10;
int n,ans;
int sum[maxn];
int tr[maxn<<5][2],cnt;
int head[maxn],tot;
struct node{
	int to,nxt,val;
}edge[maxn<<1];
inline void add(int u,int v,int w){
	edge[++tot].to=v;
	edge[tot].val=w;
	edge[tot].nxt=head[u];
	head[u]=tot;
	return;
}
inline void dfs(int u,int fa){
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to,w=edge[i].val;
		if(v==fa){
			continue;
		}
		sum[v]=sum[u]^w;
		dfs(v,u);
	}
	return;
}
inline void build(int x){
	int now=0;
	for(int i=1<<30;i>0;i>>=1){
		bool p=i&x;
		if(!tr[now][p]){
			tr[now][p]=++cnt;
		}
		now=tr[now][p];
	}
	return;
}
inline int query(int x){
	int res=0,now=0;
	for(int i=1<<30;i>0;i>>=1){
		bool p=i&x;
		if(tr[now][!p]){
			res+=i;
			now=tr[now][!p];
		}else{
			now=tr[now][p];
		}
	}
	return res;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++){
    	int u,v,w;
    	scanf("%d%d%d",&u,&v,&w);
    	add(u,v,w);
    	add(v,u,w);
	}
    dfs(1,0);
    for(int i=1;i<=n;i++){
    	build(sum[i]);
	}
	for(int i=1;i<=n;i++){
		ans=max(ans,query(sum[i]));
	}
    printf("%d\n",ans);
    return 0;
}

远古代码,写的一坨

T5

思维降智

字典树起手。钦定一个串是最小的,那么一定会得到若干字母的大小关系,显然可以图论建模

建模后直接转换成有向图判环问题,拓扑排序直接做做完了

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=3e4+5,M=3e5+5,S=26;
int n,nxt[M][S],ed[M],tot;string s[N];
int in[N],ans[N],tol;vi G[S];queue<int> q;
inline void ins(string s){
    int len=s.length(),now=0;
    for(int i=0;i<len;i++){
        int x=s[i]-'a';
        if(!nxt[now][x])nxt[now][x]=++tot;
        now=nxt[now][x];
    }
    ed[now]++;
    return;
}
inline bool ask(string s){
    int len=s.length(),now=0;
    for(int i=0;i<len;i++){
        int x=s[i]-'a';
		if(ed[now])return false;
		for(int c=0;c<26;c++){
			if(x==c||!nxt[now][c])continue;
			G[x].pb(c);in[c]++;
		}
		now=nxt[now][x];
    }
    return true;
}
inline bool judge(){
	while(!q.empty())q.pop();
	for(int i=0;i<26;i++)
		if(!in[i])q.push(i);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int v:G[u]){
			in[v]--;
			if(!in[v])q.push(v);
		}
	}
	for(int i=0;i<26;i++)
		if(in[i])return false;
	return true;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
    for(int i=1;i<=n;i++)cin>>s[i],ins(s[i]);
    for(int i=1;i<=n;i++){
		for(int i=0;i<26;i++)G[i].clear(),in[i]=0;
		if(!ask(s[i]))continue;
		if(judge())ans[++tol]=i;
    }
	cout<<tol<<endl;
	for(int i=1;i<=tol;i++)cout<<s[ans[i]]<<endl;
    return 0;
}

T6

我从未见过,如此,厚(毒)颜(瘤)无(屎)耻(山)之题

CDX:一眼类似笛卡尔树的极值分治
云落:?(又双叒叕双目失明了)

极值分治之后,考虑在通过左右半边搞出一个异或值使得其大于分治中心

嗯,很字典树

那么考虑对某一边 build 出 01 Trie,然后把另外一边放上去跑

对答案的贡献显然和二进制上的某一位的 \(0/1\) 关系有关,分讨即可(详见代码)

直接这么写,会获得 TLE 的结果

CDX:改成启发式合并就对了
云落:彳亍

然后就是一堆 shi 山的累积,一山更比一山高

(由于云落不会 \(O(n)\) 建出类似笛卡尔树的结构,所以只能手搓线段树)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e5+5;
int n,a[N],ls[N],rs[N];
int rt[N],cnt,nxt[N<<7][2],res[N<<7],ans;
struct Segment_tree{
	struct node{int l,r,mx;}tr[N<<2];
	inline int get(int x,int y){
		if(x==-1)return y;
		if(y==-1)return x;
		int o=(a[x]>=a[y])?x:y;
		return o;
	}
	inline void pushup(int u){
		tr[u].mx=get(tr[u<<1].mx,tr[u<<1|1].mx);
		return;
	}
	inline void build(int u,int l,int r){
		// cerr<<u<<' '<<l<<' '<<r<<endl;
		tr[u].l=l,tr[u].r=r;
		if(l==r){tr[u].mx=l;return;}
		int mid=(l+r)>>1;
		build(u<<1,l,mid),build(u<<1|1,mid+1,r);
		pushup(u);
		return;
	}
	inline int query(int u,int ql,int qr){
		int l=tr[u].l,r=tr[u].r;
		if(ql<=l&&qr>=r)return tr[u].mx;
		int mid=(l+r)>>1,res=-1;
		if(ql<=mid)res=get(res,query(u<<1,ql,qr));
		if(qr>mid)res=get(res,query(u<<1|1,ql,qr));
		return res;
	}
}sgt;
inline void init(int u,int l,int r){
	// cerr<<u<<' '<<l<<' '<<r<<endl;
	if(l==r)return;
	if(l!=u)ls[u]=sgt.query(1,l,u-1);
	if(u!=r)rs[u]=sgt.query(1,u+1,r);
	if(ls[u])init(ls[u],l,u-1);
	if(rs[u])init(rs[u],u+1,r);
	return;
}
inline void ins(int rt,int num){
	int now=rt;
	for(int i=20;i>=0;i--){
		int x=((num>>i)&1);
		if(!nxt[now][x])nxt[now][x]=++cnt;
		now=nxt[now][x];res[now]++;
	}
	return;
}
inline void ask(int rt,int num,int tar){
	int now=rt;
	for(int i=20;i>=0;i--){
		if(!now)return;
		int x=((num>>i)&1),v=((tar>>i)&1);
		if(x==0&&v==0)ans+=res[nxt[now][1]],now=nxt[now][0];
		else if(x==0&&v==1)now=nxt[now][1];
		else if(x==1&&v==0)ans+=res[nxt[now][0]],now=nxt[now][1];
		else if(x==1&&v==1)now=nxt[now][0];
	}
	return;
}
inline void dfs(int u,int l,int r){
	if(l==r){rt[u]=++cnt;ins(rt[u],a[u]);return;}
	if(ls[u])dfs(ls[u],l,u-1);
	if(rs[u])dfs(rs[u],u+1,r);
	if(u-l+1<=r-u+1){
		rt[u]=rt[rs[u]];
		ask(rt[u],a[u],a[u]);ins(rt[u],a[u]);
		for(int i=l;i<=u-1;i++)ask(rt[u],a[i],a[u]);
		for(int i=l;i<=u-1;i++)ins(rt[u],a[i]);
	}else{
		rt[u]=rt[ls[u]];
		ask(rt[u],a[u],a[u]);ins(rt[u],a[u]);
		for(int i=u+1;i<=r;i++)ask(rt[u],a[i],a[u]);
		for(int i=u+1;i<=r;i++)ins(rt[u],a[i]);
	}
	return;
}
signed main(){
	// freopen("in.txt","r",stdin);
	// freopen("out.txt","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	sgt.build(1,1,n);
	// cerr<<"Zyx"<<endl;
	int root=sgt.query(1,1,n);
	init(root,1,n);
	// cerr<<"----------------"<<endl;
	// for(int i=1;i<=n;i++)cerr<<i<<' '<<ls[i]<<' '<<rs[i]<<endl;
	dfs(root,1,n);
	cout<<ans<<endl;
	return 0;
}
/*
7
6 2 3 1 5 11 0
*/

T7

知周所众,当你看到 \(2s/500MB\)\(n \le 3 \times 10^6\) 时,会出现______

没错,云落被(消音)(消音)卡常了!

但这个题算个水紫,思维链并不是很长,除了卡常一无是处的屑

字典树起手,对于每一个查询串,先在字典树里搞一个前缀

然后问题转化为在字典树的一个子树里搞事情

自然会有一些用脚代替大脑,DS 代替思考的人搞出一些神秘的三维数点……

然而根本不需要,可以考虑维护子树内部所有的链(根指向叶子)哈希

开个 unordered_map 维护,问题转化为了查询串的后缀哈希与若干哈希值的判等与计数问题

这显然是个二维数点,由于查询在修改之后,甚至可以直接把字典树摊到 dfn 上跑一个差分

其实正常来说这题就差不多了,但是还有喜闻乐见的卡常与哈希冲突环节

这部分得看造化……

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define LL __int128
#define pb push_back
using namespace std;
const int N=3e6+5,K=1e5+5,B=107;
const ll MOD=((1ll<<61)-1ll);
int n,Q,ans[K];string s;ll pw[N],H[N];
int nxt[N][27],tot=1,L[N],tim,R[N],rev[N];
struct node{ll v;int id,o;};
vector<ll> t[N];vector<node> vec[N];
unordered_map<ll,int> mp;
inline void init(){
	pw[0]=1;
	for(int i=1;i<N;i++)pw[i]=((LL)pw[i-1]*B)%MOD;
	return;
}
inline ll geth(int l,int r){
	return (H[r]-(LL)H[l-1]*pw[r-l+1]%MOD+MOD)%MOD;
}
inline void ins(int len){
	int now=1;t[now].pb(geth(1,len));
	for(int i=1;i<=len;i++){
		int c=s[i]-'a'+1;
		if(!nxt[now][c])nxt[now][c]=++tot;
		now=nxt[now][c];
		t[now].pb((i==len)?0:geth(i+1,len));
	}
	return;
}
inline void dfs(int u){
	L[u]=++tim;rev[tim]=u;
	for(int i=1;i<=26;i++)
		if(nxt[u][i])dfs(nxt[u][i]);
	R[u]=tim;
	return;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	init();
	cin>>n>>Q;
	for(int i=1;i<=n;i++){
		cin>>s;int len=s.length();s=' '+s;
		for(int j=1;j<=len;j++)H[j]=((LL)H[j-1]*B+s[j])%MOD;
		ins(len);
	}
	dfs(1);
	for(int i=1;i<=Q;i++){
		cin>>s;int len=s.length();s=' '+s;
		int now=1,idx=0;
		for(int j=1;j<=len&&s[j]!='*';j++){
			int c=s[j]-'a'+1;
			if(!nxt[now][c]){ans[i]=-1;break;}
			idx=j;now=nxt[now][c];
		}
		if(ans[i]==-1)continue;
		ll sum=0;
		for(int j=idx+2;j<=len;j++)sum=((LL)sum*B+s[j])%MOD;
		vec[L[now]-1].pb({sum,i,-1});vec[R[now]].pb({sum,i,1});
	}
	for(int i=1;i<=tim;i++){
		for(ll x:t[rev[i]])mp[x]++;
		for(auto x:vec[i])ans[x.id]+=(mp[x.v]*x.o);
	}
	for(int i=1;i<=Q;i++)cout<<max(0,ans[i])<<'\n';
	return 0;
}

T8

(高效进阶二连击破)

感觉出题人是九漏鱼,语文是 oier 教的

KMP 的基础上重定义 == 的条件,树状数组维护排名

不会有人回去写主席树吧

点击查看代码
#include<bits/stdc++.h>
#define lwbd lower_bound
using namespace std;
const int N=1e6+5;
int n,m,p[N],a[N],b[N],v[N],nxt[N],tol,ans[N];
struct BIT{
	int c[N];
	inline int lb(int x){return x&(-x);}
	inline void clr(){
		for(int i=1;i<N;i++)c[i]=0;
		return;
	}
	inline void add(int x,int v){
		for(int i=x;i<N;i+=lb(i))c[i]+=v;
		return;
	}
	inline int ask(int x){
		int res=0;
		for(int i=x;i;i-=lb(i))res+=c[i];
		return res;
	}
}bit;
inline void getnxt(){
	bit.clr();int j=0;
	for(int i=2;i<=n;i++){
		while(j>=1&&bit.ask(p[i])!=v[j+1]){
			for(int x=i-j;x<i-nxt[j];x++)bit.add(p[x],-1);
			j=nxt[j];
		}
		if(bit.ask(p[i])==v[j+1])bit.add(p[i],1),j++;
		nxt[i]=j;
	}
	return;
}
inline void kmp(){
	bit.clr();int j=0;
	for(int i=1;i<=m;i++){
		while(j>=1&&bit.ask(a[i])!=v[j+1]){
			for(int x=i-j;x<i-nxt[j];x++)bit.add(a[x],-1);
			j=nxt[j];
		}
		if(bit.ask(a[i])==v[j+1])bit.add(a[i],1),j++;
		if(j==n)ans[++tol]=i-n+1;
	}
	return;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;v[n+1]=-1;
	for(int i=1,x;i<=n;i++)cin>>x,p[x]=i;
	for(int i=1;i<=m;i++)cin>>a[i],b[i]=a[i];
	sort(b+1,b+m+1);int T=unique(b+1,b+m+1)-b-1;
	for(int i=1;i<=m;i++)a[i]=lwbd(b+1,b+T+1,a[i])-b;
	for(int i=1;i<=n;i++)v[i]=bit.ask(p[i]),bit.add(p[i],1);
	getnxt();kmp();
	cout<<tol<<'\n';
	for(int i=1;i<=tol;i++)cout<<ans[i]<<' ';
	cout<<'\n';
	return 0;
}

T9

完全一样的题面,把 KMP 修改成 EXKMP 就对了,吗?

死因:字符串拼接之后没有判断 LCP 越界的情况
墨鱼死因:没有判断 z[i] 顶到边界的情况

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,m,a[N],v1[N],v2[N],z[N];
struct BIT{
	int c[N];
	inline int lb(int x){return x&(-x);}
	inline void clr(){memset(c,0,sizeof(c));return;}
	inline void add(int x,int v){
		for(int i=x;i<N;i+=lb(i))c[i]+=v;
		return;
	}
	inline int ask(int x){
		int res=0;
		for(int i=x;i;i-=lb(i))res+=c[i];
		return res;
	}
}bit;
inline bool chk(int p1,int p2){
	return v1[p1]==bit.ask(a[p2]-1)&&v2[p1]==bit.ask(a[p2]);
}
inline void getz(){
	z[1]=n;int l=1,r=1;
	
	// for(int i=1;i<=n;i++)cerr<<a[i]<<' ';
	// cerr<<endl;

	for(int i=2;i<=n;i++){
		if(i<=r)z[i]=min(z[i-l+1],r-i+1);
		if(z[i]==r-i+1){
			while(i+z[i]<=n&&z[i]+1<=n-m&&chk(z[i]+1,i+z[i])){
				bit.add(a[i+z[i]],1);
				z[i]++;
			}
		}
		if(i+z[i]-1>r)l=i,r=i+z[i]-1;
		bit.add(a[i],-1);
	}

	for(int i=1;i<=n;i++)cerr<<z[i]<<' ';
	cerr<<endl;

	return;
}
signed main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=n+1;i<=n+m;i++)cin>>a[i];
	n+=m;
	for(int i=1;i<=n;i++){
		v1[i]=bit.ask(a[i]-1);v2[i]=bit.ask(a[i]);
		bit.add(a[i],1);
	}
	bit.clr();getz();	
	for(int i=n-m+1;i<=n;i++)cout<<z[i]<<' ';
	cout<<'\n';
	return 0;
}
/*
5 15
1 5 5 2 5
2 13 10 4 11 5 2 6 11 4 9 6 11 6 1
*/

T10

一句话题意:求最短 border

直接先求出 nxt 数组,然后递归找最短 border

但是递归计算所有的最短 border 会超时,考虑浅浅地记忆化一手

就过了……

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,nxt[N],ans;string s;
inline void getnxt(){
	int j=0;
	for(int i=2;i<=n;i++){
		while(s[i]!=s[j+1]&&j>=1)j=nxt[j];
		if(s[i]==s[j+1])j++;
		nxt[i]=j;
	}
	return;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>s;s=' '+s;
	getnxt();
	for(int i=2,j=2;i<=n;i++,j=i){
		while(nxt[j])j=nxt[j];
		if(nxt[i])nxt[i]=j;
		ans+=i-j;
	}
	cout<<ans<<endl;
	return 0;
}

T11

谁懂啊被 map 和模数类型为 long long 背刺的破碎感

好不容易卡常之后,给我直接来一手

Wrong Answer on #61

(消音)哈希冲突,我(消音)(消音)(消音)

吐槽时间结束,进入正题

枚举 \(l,r\) 然后直接用状压异或的方式判断是否行上有回文

如果有,那么直接搞一个集合(可重复)哈希

因为列回文等价于相对应的行可重集合相等

所以集合哈希搞出来,在列上跑 Manacher 即可

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=255,MOD=1e8+7,OwO=10086;
int n,m,rnd[30];string str[N];
bool vis[N][N][N];int H[N][N][N];
unordered_map<int,int> mp;int occ=1022,tol;
int s[N<<1],S[N],p[N<<1],ans;
mt19937 RD(time(NULL));
inline int ppc(int x){return __builtin_popcount(x);}
inline int Manacher(int n){
	n=n*2+1;
	for(int i=1;i<=n;i++)s[i]=((i&1)?OwO:S[i>>1]);
	int c=0,r=0,len=0;
	for(int i=1;i<=n;i++){
		len=(r>i?min(p[c*2-i],r-i):1);
		while(i+len<=n&&i-len>=1&&s[i+len]==s[i-len])len++;
		if(i+len>r)r=i+len,c=i;
		p[i]=len;
	}
	int res=0;
	for(int i=1;i<=n;i++){
		res+=p[i];res-=(p[i]+1)/2;
	}
	return res;
}
inline void work(int l,int r){
	int len=0;
	for(int i=1;i<=n;i++){
		if(!mp[H[i][l][r]]){
			ans+=Manacher(len);len=0;
			continue;
		}
		S[++len]=mp[H[i][l][r]];
	}
	ans+=Manacher(len);len=0;
	return;
}
signed main(){
	// freopen("in.txt","r",stdin);
	// freopen("out.txt","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	for(int i=1;i<=26;i++){
		uniform_int_distribution<int> V(19800107,20091023);
		rnd[i]=V(RD);
	}
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>str[i],str[i]=' '+str[i];
	for(int i=1;i<=n;i++){
		for(int l=1;l<=m;l++){
			int st=(1<<(str[i][l]-'a'+1));
			int ad=rnd[(str[i][l]-'a'+1)];ll ml=rnd[(str[i][l]-'a'+1)];
			vis[i][l][l]=true;H[i][l][l]=(0ll+ml+ad)%MOD;
			if(!mp[H[i][l][l]])mp[H[i][l][l]]=++tol;
			for(int r=l+1;r<=m;r++){
				int c=str[i][r]-'a'+1;
				st^=(1<<c);ad=(ad+rnd[c])%MOD;ml=(1ll*ml*rnd[c])%MOD;
				if(st==0||ppc(st)==1){
					vis[i][l][r]=true;H[i][l][r]=(0ll+ml+ad)%MOD;
					if(!mp[H[i][l][r]])mp[H[i][l][r]]=++tol;
				}
			}
		}
	}

	for(int l=1;l<=m;l++)
		for(int r=l;r<=m;r++)
			work(l,r);
	cout<<ans<<'\n';
	return 0;
}

T12

不会,挖坑,待填

Upd. 突然就会了

不需要搞一些奇怪的回文判定,故抛弃 PAM,拥抱 Manacher

起手 Manacher,接下来所有的字符串均指扩展串。先刨除单个分隔符作为回文串出现的贡献(扩展串)

考虑单次查询 \([l,r]\) 怎么做

对于 \(\forall i \in [l,r]\),其对答案的贡献为 \(\min (p_i ,\ i-l ,\ r-i)\)

三个值不好做,直接拆分成 \([l,mid]\)\([mid+1,r]\) 两部分,去掉一维限制去做

\([l,mid]\)\([mid+1,r]\) 本质上是对称的,只做一边,另一边照搬就行

现在问题转化为,对于 \(\forall i \in [l,mid]\),对答案的贡献为 \(\min (p_i ,\ i-l)\)

经典线段树维护回文子串,一个 \(p_i\) 相当于在 \([i-p_i+1,i]\) 做区间加 \(1\)

多次查询咋办?离线扫描线直接做做完了

感觉没有 CDX 说的那么难写是怎么肥四捏?

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e5+5;
int n,Q,p[N],ans[N];char str[N],s[N];
struct que{int l,r,id;}q1[N],q2[N];
struct Segment_tree{
    struct node{int l,r,sum,tag;}tr[N<<2];
    inline void pushup(int u){
        tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
        return;
    }
    inline void pushdown(int u){
        if(!tr[u].tag)return;
		int v=tr[u].tag;
        tr[u<<1].sum+=((tr[u<<1].r-tr[u<<1].l+1)*v);
        tr[u<<1|1].sum+=((tr[u<<1|1].r-tr[u<<1|1].l+1)*v);
        tr[u<<1].tag+=v;tr[u<<1|1].tag+=v;
        tr[u].tag=0;
        return;
    }
    inline void build(int u,int l,int r){
        tr[u].l=l;tr[u].r=r;tr[u].tag=0;
        if(l==r){tr[u].sum=0;return;}
        int mid=(l+r)>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
        return;
    }
    inline void modify(int u,int ql,int qr,int k){
        int l=tr[u].l,r=tr[u].r;
        if(ql<=l&&qr>=r){
			tr[u].sum+=((r-l+1)*k);
			tr[u].tag+=k;
            return;
        }
        pushdown(u);
        int mid=(l+r)>>1;
        if(ql<=mid)modify(u<<1,ql,qr,k);
        if(qr>mid)modify(u<<1|1,ql,qr,k);
        pushup(u);
        return;
    }
    inline int query(int u,int ql,int qr){
        int l=tr[u].l,r=tr[u].r;
        if(ql<=l&&qr>=r)return tr[u].sum;
        pushdown(u);
        int mid=(l+r)>>1,res=0;
        if(ql<=mid)res+=query(u<<1,ql,qr);
        if(qr>mid)res+=query(u<<1|1,ql,qr);
        return res;
    }
}sgt;
inline bool cmp1(que x,que y){return x.r<y.r;}
inline bool cmp2(que x,que y){return x.l>y.l;}
inline void Manacher(){
	n=n*2+1;
	for(int i=1;i<=n;i++)s[i]=((i&1)?'#':str[i>>1]);
	int c=0,r=0,len=0;
	for(int i=1;i<=n;i++){
		len=(r>i?min(p[c*2-i],r-i):1);
		while(i+len<=n&&i-len>=1&&s[i+len]==s[i-len])len++;
		if(i+len>r)r=i+len,c=i;
		p[i]=len;
	}
	return;
}
inline void work_l(){
	sort(q1+1,q1+Q+1,cmp1);
	sgt.build(1,1,n);int j=1;
	for(int i=1;i<=n;i++){
		sgt.modify(1,i-p[i]+1,i,1);
		while(j<=Q&&q1[j].r<=i){
			ans[q1[j].id]+=sgt.query(1,q1[j].l,q1[j].r);
			j++;
		}
	}
	return;
}
inline void work_r(){
	sort(q2+1,q2+Q+1,cmp2);
	sgt.build(1,1,n);int j=1;
	for(int i=n;i>=1;i--){
		sgt.modify(1,i,i+p[i]-1,1);
		while(j<=Q&&q2[j].l>=i){
			ans[q2[j].id]+=sgt.query(1,q2[j].l,q2[j].r);
			j++;
		}
	}
	return;
}
signed main(){
	// freopen("qwq.in","r",stdin);
	// freopen("qwq.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>Q>>(str+1);
	Manacher();
	for(int i=1;i<=n;i++)cerr<<p[i]<<' ';
	for(int i=1;i<=Q;i++){
		int l,r;cin>>l>>r;ans[i]-=(r-l+2);
		l<<=1;l--;r<<=1;r++;
		int mid=(l+r)>>1;
		q1[i]={l,mid,i},q2[i]={mid+1,r,i};
	}
	work_l(),work_r();
	for(int i=1;i<=Q;i++)cout<<ans[i]/2<<'\n';
	return 0;
}

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-22 21:05  sunxuhetai  阅读(9)  评论(0)    收藏  举报