2025/7/15 字符串专题

2025/7/15 \(\mathbf{} \begin{Bmatrix} \frac{{\Large LEARN} }{{\color{Yellow}\Large Record} }\mathbf{} {No.7} \end{Bmatrix}\times{}\) NeeDna

CF1914G2

题目大意

有一个长度为 \(2n\) 的带颜色序列,颜色 \(1\sim n\) 各出现两次,初始你可以标记任意点集 \(S\)

然后,你可以不断进行两个操作。

  1. 选择一个颜色,如果其中一个数被标记了,标记另一个数。

  2. 选择一个颜色,两个数都被标记了,标记中间的数。

如果要标记所有的数,求 \(|S|\) 的最小值即此时的 \(S\) 的可能个数。

做法

题目中的操作我们试一下,发现会有许多个单独的区间,互不可达。

我么称这种序列叫封闭序列。

显然一个这样的区间可以只用一个操作点亮。

接下来考虑第二问。

要求的就是区间内有多少个点可以到达边界。

  • 1.图论做法:我们可以让区间内一个点向其的区间连边,那么这就是一个连通性问题了(跑tarjian找scc)最后topu找区间内入度为0的点(和边界强联通),这个边数过多,用线段树优化建图。O(nlogn)

  • 2.哈希做法:一个点假如不能造成贡献,那么它会属于一个子封闭序列,用一个lst数组+前缀和判断区间点是否合法(我并没有搞懂)。

https://oi-wiki.org/ds/persistent-trie/

P4735 最大异或和

题目描述

给定一个非负整数序列 \(\{a\}\),初始长度为 \(N\)

\(M\) 个操作,有以下两种操作类型:

  1. A x:添加操作,表示在序列末尾添加一个数 \(x\),序列的长度 \(N\)\(1\)

  2. Q l r x:询问操作,你需要找到一个位置 \(p\),满足 \(l \le p \le r\),使得:\(a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x\) 最大,输出最大值。

最大异或其实就是跑01trie,这道题是动态的,有在线和离线做法。

  • 在线做法,可持久化01trie,类似主席树,一个答案合法在两个trie上跑差分即可。

  • 离线做法,按照查询的r从小到大排序,满足条件大于l和最大距离即可。

CF1535F String Distance

只讲收获,这道题太难了:

  • 在对字符串从小到大排序后,1对于i的LCP(最长公共前缀)是单调递减的.

  • 前后缀维护可以用trie。

-里面保证单调不减即可,跑一遍就可以处理O(N^3)差点,不会了。

CF2023C C+K+S

首先排除1个团都是出点,入点。这时候不会成环。

首先得知道如何利用每个环的长度都是 \(k\) 的倍数的条件。

这里直接给出结论,一个图满足每个环的长度都是 \(k\) 的倍数的充要条件是存在一种 \(k\) 染色(设第 \(i\) 个点的颜色是 \(c_i(c_i\in [0,k-1])\))使得对于任意一条边 \(u\rightarrow v\)\(c_v=(c_u+1)\mod k\)。证明比较简单,就不提了。

知道这个结论这题就好做了,题意就可以转化为先分别对图 \(G_1,G_2\) 进行 \(k\) 染色,然后两个图的点按要求进行匹配(既要满足 \(a,b\) 的要求,也要满足颜色的要求)。

另一个显然的结论是 \(G_1\) 只有 \(k\) 种不同的染色方式(让 \(c_1\) 分别从 \(0\) 取到 \(k-1\)),然后对于每一种染色方式都要与 \(G_2\) 进行匹配。

判断是否存在一个完美匹配是容易的,直接把点按照颜色分类统计其数量。对于 \(G_2\) 中颜色为 \(c\)\(b_i=0\) 的点,应该与 \(G_1\) 中颜色为 \((c+k-1)\bmod k\)\(a_i=1\) 的点匹配,要存在完美匹配就等价于两种颜色对应的点的个数应该一。

剩下的情况类似。对于 \(k\) 种不同的染色方式,发现其按颜色分类后的数组就是在不断的循环移位,倍长后 kmp/hash 匹配即可。时间复杂度线性。

CF432D Prefixes and Suffixes

一眼就感觉是 \(kmp\), 因为 \(nxt\) 数组天生就是干这种事情的

答案集合就是从 \(n\)\(nxt\) 所能跳到的所有节点,因为nxt定义就是最长前后缀。

那个出现次数怎么搞?

显然可以 \(dp\)(其实是枚举)。

\(dp[i]\) 表示跳到 \(i\) 的方案数。

然后倒叙枚举 \(i\), \(dp[nxt[i]]+=dp[i]\)(这是一个充分条件,所以可以直接加)

signed main() {
    scanf("%s", s + 1) ;
    n = strlen(s + 1) ;
    nxt[1] = 0 ;
    for (int i = 2, j = 0; i <= n; i++) {
        while (j && s[j + 1] != s[i]) j = nxt[j] ;
        if (s[j + 1] == s[i]) j++ ;
        nxt[i] = j ;
    }
    for (int i = nxt[n]; i; i = nxt[i]) ans.pb(i) ;
    clr(dp) ;
    per(i, n, 1) {
        dp[i]++ ;
        dp[nxt[i]] += dp[i] ;
    }
    printf("%d\n", siz(ans) + 1) ;
    per(i, siz(ans) - 1, 0) printf("%d %d\n", ans[i], dp[ans[i]]) ;
    printf("%d %d\n", n, dp[n]) ;
}

CF1849C Binary String Copying

给您一个由 \(n\) 个字符 0 和/或 1 组成的字符串 \(s\)

你将这个字符串复制 \(m\) 份,让 \(i\) /th份成为字符串 \(t_i\) 。然后在每个副本上执行一个操作:对于 \(i\) \th副本,对其子串 \([l_i; r_i]\) (从 \(l_i\) \th字符到 \(r_i\) \th字符的子串,包括两个端点)进行排序。注意,每个操作只影响一个副本,每个副本只受一个操作的影响

你的任务是计算 \(t_1, t_2, \ldots, t_m\) 中不同字符串的数量。请注意,只有在操作后至少有一个副本保持不变时,才应计算初始字符串 \(s\) 的数量。

解法

我想的比较无脑,直接用HASH暴力求串改前改后值,我们把一个串分成3个部分

  • 前端不变的部分

  • 中间排序部分

  • 最后不变部分

第一段和第三段都很好处理,用进制HASH(理解成31进制,1e9+7进制)。

第二段看一下结构必然是先一段0然后一段1,所以我们前缀和一下,就可以快速求出有多少个1了。

三段合起就做完了,

记得开long long

code:

#include<bits/stdc++.h>
#define int long long
#include<unordered_map>
using namespace std;
const int N=2e5+10,base=1000000007;
string s;
int n,m,hsh[N],pw[N],ans,a1[N],qz[N];
unordered_map<int,int> mp;
int idx(int l,int r){
	int x1=hsh[l-1]*pw[n-l+1];
	int x2=hsh[n]-hsh[r]*pw[n-r];
	int x3=a1[qz[r]-qz[l-1]]*pw[n-r];
	return x1+x2+x3;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int T;
	cin>>T;pw[0]=1;
	for(int i=1;i<N;i++){
		pw[i]=pw[i-1]*base;
		a1[i]=a1[i-1]*base+1;
	}
	while(T--){
		mp.clear();ans=0;
		cin>>n>>m;
		cin>>s;s=" "+s;
		for(int i=1;i<=n;i++){
			hsh[i]=hsh[i-1]*base+s[i]-'0';
			qz[i]=qz[i-1]+s[i]-'0';
		}
		for(int i=1,l,r;i<=m;i++){
			cin>>l>>r;
			if(!mp[idx(l,r)]){
				mp[idx(l,r)]=1;
				ans++;
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
} 

AT_arc071_c [ARC071E] TrBBnsformBBtion

题目描述

考虑对一个只含 AB 的字符串的如下操作:

  1. 将一个 A 替换成 BB,或将一个 B 替换成 AA

  2. 将三个连续相同的字符(AAABBB)消掉

例如说,串 ABA 可以通过第一个操作变成 BBBA,串 BBBAAAA 可以通过第二个操作变成 BBBA.

这些操作可以以任意顺序,不限次数地进行。

给出两个串 \(S\)\(T\),以及 \(q\) 次询问 \(a_i, b_i, c_i, d_i\),每次询问你需要回答 \(S_{a_i...b_i}\) 这一子串是否能通过这两个操作变成 \(T_{c_i...d_i}\).

解答:

首先思考A和B会如何转换

发现A->BB(题里有)而且BB->AAAA,AAAA->A。发现BB->A,A->BB 都是可以的。

那么我们可以把串变成只有一种字母(以A为例),B->AA,所以串场就是A的个数+B的个数乘2

前缀和一下就做完了。

AT_abc257_g [ABC257G] Prefix Concatenation

问题陈述

给你两个由小写英文字母组成的字符串 \(S\)\(T\)

求最小正整数 \(k\) ,使得你可以选择 \(S\)\(k\) 前缀(不一定是不同的),使它们的连接与 \(T\) 重合。

换句话说,找出最小的正整数 \(k\) ,使得在 \(1\)\(|S|\) 之间存在一个由整数组成的 \(k\) 元组 \((a_1,a_2,\ldots, a_k)\) ,使得
\(T=S_{a_1}+S_{a_2}+\cdots +S_{a_k}\) ,其中 \(S_i\) 表示从 \(1\) -st 到 \(i\) -th 字符的 \(S\) 的子串,而 \(+\) 表示字符串的连接。

如果无法使其与 \(T\) 重合,则打印 \(-1\)

解法:

这道题细节有点多,可以看看代码。

我的解法是字符串HASH二分(右查找)+贪心。

首先令 \(n\) 表示 \(S\) 的长度,\(m\) 表示 \(T\) 的长度,给 \(S,T\) 前各加一个空格,这样字符串下标就从 \(1\) 开始。

和其它解法一样,我们先用哈希+二分枚举出 \(T_i\sim T_m\)\(S\) 的最长公共前缀长度,记作 \(len_i\)

然后以每个 \(i(1\le i\le m)\) 为左端点,\(i+len_i-1\) 为右端点创建区间(\(len_i=0\) 的不用创建)。

将每个区间按照左端点升序排序(如果你是按顺序创建的区间,那区间已经有序),用一个指针 \(j\),表示当前所在字符串 \(T\) 的位置,初始 \(j=1\)

开一个装区间(结构体)的堆,内部封装按右端点升序的排序逻辑。每一次找出所有左端点 \(\le j\) 的区间扔进堆里,再取出堆中右端点最大的区间,让指针 \(j\) 移到区间的右端点 \(+1\) 的位置。最后求一共经历了几个区间就是答案。

这个方法相当于把前缀看成区间,求最少用多少个区间才能将 \(1\sim m\) 完全覆盖。

时间复杂度 \(O(m \log m)\)

code:

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=5e5+10,base=10037;
int n1,n2,hsh1[N],hsh2[N],len[N],pw[N],ans,now=1;
string s1,s2;
int haf(int x){
	int l=-1,r=min(n1,n2)+1;
	while(l<r-1){
		int mid=l+r>>1;
		if(hsh1[mid]==hsh2[x+mid-1]-hsh2[x-1]*pw[mid]) l=mid;
		else r=mid;
	}return l;
} 
priority_queue<int> q;
signed main(){
	cin>>s1>>s2;pw[0]=1;
	n1=s1.size();n2=s2.size();
	s1=" "+s1,s2=" "+s2;
	for(int i=1;i<N;i++) pw[i]=pw[i-1]*base;
	for(int i=1;i<=n1;i++) hsh1[i]=hsh1[i-1]*base+s1[i]-'a'+1;
	for(int i=1;i<=n2;i++) hsh2[i]=hsh2[i-1]*base+s2[i]-'a'+1;
    for(int i=1;i<=n2;i++) len[i]=haf(i);
	if(!len[1]){cout<<-1;return 0;}
    q.push(len[1]);
	while(now<=n2){
		int t=q.top()+1;
		for(int i=now;i<=t;i++){
			if(len[i])q.push(len[i]+i-1);
		}if(q.top()<t&&t!=n2+1){
			cout<<-1;return 0;
		}ans++;now=t;
	}cout<<ans;
	return 0;
} 

AT_abc150_f [ABC150F] Xor Shift

题目描述

给定两个长度为 \(n\) 的序列 \(a=\{a_0,a_1,\cdots,a_{n-1}\}\)\(b=\{b_0,b_1,\cdots,b_{n-1}\}\),输出所有有序数对 \((k,x)\) ,满足:

  1. \(0\leq k<n\)\(x\geq 0\)
  2. 序列 \(a'=b\),其中 \(a'_i = a_{i+k\bmod n}\operatorname{xor} x\ (0\leq i<n)\),“\(\operatorname{xor}\)”表示按位异或。

做法

看到异或先拆位,就有很好的性质了。

我们读题发现,假如 \(k\) 确定了,那么对应的 \(x\) 也就确定了。而且拆完位上的数偏移之后每一位的值异或 \(b\) 的每一位的位值都是一样的。所以我们哈希一下,判断上述性质是否在所有位上都存在,复杂度 \(O(30n)\)

详解:

根据 \(\oplus\) 的性质可以得知:若某个循环移位有解,则对于每一个二进制位都有 \(a\) 中该二进制位异或 \(x\) 的该二进制位的值后左循环移位 \(k\) 位之后与 \(b\) 相同。

假定我们在这一位不进行异或,考虑这一个问题,我们发现可以用 Hash 做:具体的,计算区间 Hash 值,在循环移位 \(k\) 位之后 \(a_{1,\cdots,k-1}\) 会对应到 \(a'_{n-k+1,\cdots,n}\),以及 \(a_{k,\cdots,n}\) 会对应到 \(a'_{1,\cdots,n-k}\)。考虑使用区间哈希值判断两段是否相等即可。

注意到在非异或的情况下判断是 \(O(n)\) 的,异或一边再判也一样,故对于每一位判断异或和非异或的情况总时间复杂度是 \(O(n\log n)\) 的。

某一个循环移位有解,当且仅当其每一位都有解,若有解则 \(x=a_{k+1} \oplus b_1\),输出即可。

CF1943B Non-Palindromic Substring

题意

如果至少存在一个长度为 \(k\) 的子串 \(^\dagger\) 不是回文 \(^\ddagger\) ,那么这个字符串 \(t\) 可以说是 \(k\) -good 的。让 \(f(t)\) 表示 \(k\) 的所有值之和,使得字符串 \(t\)\(k\) /好的。

给你一个长度为 \(n\) 的字符串 \(s\) 。您需要回答以下问题中的 \(q\) 个:

  • 给定 \(l\)\(r\),求 \(f(s_ls_{l + 1}\ldots s_r)\) 的值。

\(^\dagger\) 字符串 \(z\) 的子串是来自 \(z\) 的连续字符段。例如," \(\mathtt{defor}\) "、" \(\mathtt{code}\) "和" \(\mathtt{o}\) "都是" \(\mathtt{codeforces}\) "的子串,而" \(\mathtt{codes}\) "和" \(\mathtt{aaa}\) "则不是。

\(^\ddagger\) 回文字符串是指前后读法相同的字符串。例如,字符串" \(\texttt{z}\) "、" \(\texttt{aa}\) "和" \(\texttt{tacocat}\) "是回文字符串,而" \(\texttt{codeforces}\) "和" \(\texttt{ab}\) "不是。

解法

定义 \(k\)-good 字符串是:存在非回文的长度为 \(k\) 的子串的字符串。

很明显对于大部分的 \(k\) 和字符串 \(s\),一个字符串都是 \(k\)-good 的。那我们就观察一下非 \(k\)-good 的字符串有什么性质。

首先,所有字符串都不是 \(1\)-good 的。
如果 \(|s|=k\) 并且 \(s\) 是回文串,那么 \(s\) 不是 \(k\)-good 的。

\(s\) 不是 \(k\)-good 的,并且 \(k<|s|\),那么 \(s_1s_2s_3\dots s_k\)\(s_2s_3s_4\dots s_{k+1}\) 都是回文串。所以 \(s_1=s_k=s_3=s_{k-2}=s_5=\dots=s_{2x+1}=s_{k-2y}\)

如果 \(k\) 是偶数,就有 \(s\) 的所有字符相等,即 \(s_i=s_j\)

如果 \(k\) 是奇数,就有 \(s\) 是由两个字符交替出现构成的,即 \(s_{2x+c}=s_{2y+c}\)

我们可以说明以上命题的逆命题也成立。

所以,一个字符串:

  • 一定不是 \(1\)-good 的;
  • \(k=|s|\) 时,当且仅当这个字符串回文时不是 \(|s|\)-good 的;
  • \(k=2x+1<|s|\) 时,当且仅当所有字符都相同时不是 \(k\)-good 的;
  • \(k=2x<|s|\) 时,当且仅当是由两个字符交替构成的(即 abababab 的形式)时不是 \(k\)-good 的。

所以,我们需要快速判断一个字符串是否回文,是否所有字符相同,是否交替相同。判断完之后等差数列求和即可。

判断回文可以用 manacher 预处理或者哈希,后两个可以通过统计 \(s_i=s_{i-1}\)\(s_i=s_{i-2}\) 的位置个数来判断。

时间复杂度 \(O(\sum |s|+\sum q)\)

code:

#include<bits/stdc++.h>
using namespace std;
int T;
int n,Q;
int p[400005];
char s[400005];
int s1[200005],s2[200005];

int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&Q);
		scanf("%s",s+1);
    	for(int i=1;i<=n;i++){
    		s1[i]=s1[i-1]+(s[i]==s[i-1]);
    		if(i>1)s2[i]=s2[i-1]+(s[i]==s[i-2]);
		}
		s[0]='~',s[n+n+2]='}';
		for(int i=n;i>=1;i--)s[i+i]=s[i];
		for(int i=1;i<=n+1;i++)s[i+i-1]='|';
		int r=0,mid=0;
		for(int i=0;i<=n+n+1;i++)p[i]=0;
		for(int i=1;i<=n+n;i++){
	    	if(i<=r)p[i]=min(p[(mid<<1)-i],r-i+1);
	    	while(s[i-p[i]]==s[i+p[i]])p[i]++;
	    	if(p[i]+i>r)r=p[i]+i-1,mid=i;
    	}
		while(Q--){
			int L,R;
			scanf("%d%d",&L,&R);
			int len=(R-L+1);
			long long ans=1LL*len*(len+1)/2-1;
			int le=len-(len&1);
			int lo=len-(1^len&1);
			if(s2[R]-s2[L+1]==len-2)ans-=1LL*(3+lo)*(lo/2)/2;
			else if(len%2==1 && p[L+R]>=len)ans-=lo;
			if(s1[R]-s1[L]==len-1)ans-=1LL*(2+le)*(le/2)/2;
			else if(len%2==0 && p[L+R]>=len)ans-=le;
			printf("%lld\n",ans);
		}
	}
	return 0;
}

CF1951E


题意简述

划分给定字符串,使每一段均不回文或报告无解,多组数据。

分析

猜结论

非回文串的答案显然。

对于回文串,显然至少划分为 \(2\) 段。

通过自行举例等方式可以猜想若有解存在一种解使得原串划分为 \(2\) 段。

证明

假设法

假设存在某个回文串 \(S\)(下标由 \(1\) 开始,长度为 \(n\)),它有解但至少要划分为 \(k\)\((k \ge 3)\)

分类讨论:

  1. 假设 \(k=3\),一种解中 \(S\) 被划分为 \(S[1..x],S[x+1\dots y],S[y+1\dots n]\)

那么:

  • 划分出的串都不回文,即 \(S[1..x],S[x+1\dots y],S[y+1\dots n]\) 不回文。

这可以表示为以下三个式子:

\[\exists 0\le i\le x-1,S[1+i]\neq S[x-i] \]

\[\exists 0\le i\le y-x-1,S[x+1+i]\neq S[y-i]\space (1) \]

\[\exists 0\le i\le n-y-1,S[y+i+1]\neq S[n-i] \]

  • 合并任意两个串,得到的串一定回文,即 \(S[1\dots y],S[x+1\dots n]\) 不回文。

否则合并后得到划分为 \(2\) 段的方案,与假设矛盾。

该性质同样可以表示为以下两个式子:

\[\forall 0\le i\le y-1,S[1+i]=S[y-i]\space (2) \]

\[\forall 0\le i\le n-x-1,S[x+1+i]=S[n-i]\space (3) \]

  • 已经假设 \(S\) 为回文串。

式子表示:

\[\forall 0\le i\le n-1,S[1+i]=S[n-i]\space (4) \]

若我们合并上述的 \((2)(3)(4)\) 式,可得:

\[\forall 0\le i\le \min(y-1,n-x-1,n-1),S[1+i]=S[y-i]=S[x+1+i]=S[n-i] \]

而分析上述字符串长度或分析 \(x,y,n\) 大小可知,

\[y-x\le \min(y,n-x,n) \]

所以

\[y-x-1\le \min(y-1,n-x-1,n-1) \]

所以

\[\forall 0\le i\le y-x-1,S[1+i]=S[y-i]=S[x+1+i]=S[n-i] \]

我们发现,该式与前述 \((1)\) 式矛盾,故假设不成立,\(k\neq 3\)

  1. 假设 \(k>3\),一种解中划分开头的 \(3\) 段为 \(S[1\dots x],S[x+1\dots y],S[y+1\dots z]\space(z<n)\),对 \(S[1\dots z]\) 作类似的分析也可得假设不成立,\(k \le 3\)

综上所述,原假设不成立,一定存在一种解使得回文串 \(S\) 被划分为 \(2\) 段。

代码实现

知道了这一点后,利用 Hash 判断回文,枚举断点即可以 \(O(\sum n)\) 通过本题。 这证明好难2000?????

posted @ 2025-07-15 14:02  NeeDna  阅读(34)  评论(0)    收藏  举报