T1 子串查找
这道题在这篇文章中曾作为例题讲解,不再赘述
其实就是青少年懒惰综合征有点严重(医院证明见帖子底部)
T2 friends
题库里面又没找到
考点:哈希
我们在完成输入后,需要进行一个简单的判断:如果输入的字符串长度为偶数,直接无解
否则,求得字符串对应的哈希值
接下来,依次枚举每个中间点,判断它能否成为中间所插入的字符
如果可以,则判断有无多解
循环结束后,判断有没有找到一组解
有,则输出解;无,则无解
难点主要在于依次枚举每个中间点时,判断它能否成为中间所插入的字符
即求得两个不一定连在一起的字符串的哈希值是否相同
情况1: i < n ÷ 2 + 1 i<n\div2+1 i<n÷2+1
如上图(丑不拉几的),我们考虑左边的字符串的哈希值
它被分为了两部分, i i i 左边的和 i i i 右边的
处理 i i i 左边的,显然,其结果就是 h a s h [ i − 1 ] hash[\ i-1\ ] hash[ i−1 ] ,但是,因为两部分字符串的哈希值不能直接拼在一起,需要乘上相应的进制,即 P o w [ n ÷ 2 − i + 1 ] Pow[\ n\div2-i+1\ ] Pow[ n÷2−i+1 ] (其中, n ÷ 2 + 1 n\div2+1 n÷2+1 表示右边字符串的长度)
处理 i i i 右边的,显然,我们要用较长一段的字符串哈希值减去较短一段的字符串哈希值乘上相应的相应的进制,即 h a s h [ n ÷ 2 + 1 ] − h a s h [ i ] × P o w [ n ÷ 2 − i + 1 ] hash[\ n\div2+1\ ]-hash[\ i\ ]\times Pow[\ n\div2-i+1\ ] hash[ n÷2+1 ]−hash[ i ]×Pow[ n÷2−i+1 ]
拼在一起,就有了左边字符串的哈希值: h a s h [ i − 1 ] × P o w [ n ÷ 2 − i + 1 ] + h a s h [ n ÷ 2 + 1 ] − h a s h [ i ] × P o w [ n ÷ 2 − i + 1 ] hash[\ i-1\ ]\times Pow[\ n\div2-i+1\ ]+hash[\ n\div2+1\ ]-hash[\ i\ ]\times Pow[\ n\div2-i+1\ ] hash[ i−1 ]×Pow[ n÷2−i+1 ]+hash[ n÷2+1 ]−hash[ i ]×Pow[ n÷2−i+1 ]
考虑右边字符串的哈希值:
其实就简单多了:用较长一段的字符串哈希值减去较短一段的字符串哈希值乘上相应的相应的进制,即 h a s h [ n ] − h a s h [ n ÷ 2 + 1 ] × P o w [ n − n ÷ 2 − 1 ] hash[\ n\ ]-hash[\ n\div2+1\ ]\times Pow[\ n-n\div2-1\ ] hash[ n ]−hash[ n÷2+1 ]×Pow[ n−n÷2−1 ]
情况2: i = n ÷ 2 + 1 i=n\div2+1 i=n÷2+1
如上图:我们考虑右边的字符串(左边的字符串巨简单,就一个 h a s h [ n ] hash[\ n\ ] hash[ n ] 即可)
其实也很简单:用较长一段的字符串哈希值减去较短一段的字符串哈希值乘上相应的相应的进制,即 h a s h [ n ] − h a s h [ n ÷ 2 + 1 ] × P o w [ n − n ÷ 2 − 1 ] hash[\ n\ ]-hash[\ n\div2+1\ ]\times Pow[\ n-n\div2-1\ ] hash[ n ]−hash[ n÷2+1 ]×Pow[ n−n÷2−1 ]
跟上面那个不是一毛一样吗?
情况2: i > n ÷ 2 + 1 i>n\div2+1 i>n÷2+1
考虑右边的字符串的哈希值(左边的依旧是 h a s h [ n ÷ 2 ] hash[\ n\div2\ ] hash[ n÷2 ])
右边的字符串是由两部分拼接而成,依次考虑
对于 i i i 左边的字符串,我们可以得出其原哈希值为 h a s h [ i − 1 ] − h a s h [ n ÷ 2 ] × P o w [ i − n ÷ 2 − 1 ] hash[\ i-1\ ]-hash[\ n\div2\ ]\times Pow[\ i-n\div2-1] hash[ i−1 ]−hash[ n÷2 ]×Pow[ i−n÷2−1] ,但是,因为要将两个字符串拼在一起,所以在此基础上还要乘上 P o w [ n − i ] Pow[\ n-i\ ] Pow[ n−i ] ( n − i n-i n−i 为 i i i 右边的字符串的长度)
对于 i i i 右边的字符串,其哈希值是很容易求解的: h a s h [ n ] − h a s h [ i ] × P o w [ n − i ] hash[\ n\ ]-hash[\ i\ ]\times Pow[\ n-i\ ] hash[ n ]−hash[ i ]×Pow[ n−i ]
将两个哈希值加在一起得到: ( h a s h [ i − 1 ] − h a s h [ n ÷ 2 ] × P o w [ i − n ÷ 2 − 1 ] ) × P o w [ n − i ] + h a s h [ n ] − h a s h [ i ] × P o w [ n − i ] (hash[\ i-1\ ]-hash[\ n\div2\ ]\times Pow[\ i-n\div2-1\ ]\ )\times Pow[\ n-i\ ]+hash[\ n\ ]-hash[\ i\ ]\times Pow[\ n-i\ ] (hash[ i−1 ]−hash[ n÷2 ]×Pow[ i−n÷2−1 ] )×Pow[ n−i ]+hash[ n ]−hash[ i ]×Pow[ n−i ]
细节实现见代码:
#include<cstdio>
char a[2000005];
long long int hash[2000005],Pow[2000005];
int main(){
int n;
scanf("%d",&n);
if(n%2==0){ //初判无解
printf("NOT POSSIBLE");
return 0;
}
scanf("%s",a+1);
Pow[0]=1;
for(int i=1;i<=n;i++){
Pow[i]=Pow[i-1]*131;
hash[i]=hash[i-1]*131+a[i]-'A';
} //初始化
int x,y,ans=0,kkksc03,tot=0;
bool flag=0;
for(int i=1;i<=n;i++){
if(i<=n/2){ //三种情况依次讨论,上文已提
x=hash[i-1]*Pow[n/2-i+1]+hash[n/2+1]-hash[i]*Pow[n/2-i+1];
y=hash[n]-hash[n/2+1]*Pow[n-n/2-1];
}else if(i==n/2+1){
x=hash[n/2];
y=hash[n]-hash[n/2+1]*Pow[n-n/2-1];
}else{
x=hash[n/2];
y=(hash[i-1]-hash[n/2]*Pow[i-1-n/2])*Pow[n-i]+hash[n]-hash[i]*Pow[n-i];
}
if(x==y){ //两边哈希值相等,有解
if(flag==1&&ans!=x){ //若与之前找到的解的哈希值不一样
printf("NOT UNIQUE"); //多解
return 0;
}
flag=1,ans=x,kkksc03=i; //标记有解,记录哈希值以及下标
}
}
if(flag==0){ //找了一遍还是无解
printf("NOT POSSIBLE");
return 0;
}
for(int i=1;i<=n;i++){
if(i!=kkksc03){ //当与插入字符的下标不同时
printf("%c",a[i]); //输出
tot++;
if(tot==n/2){ //当输出的字符数已经足够了,就结束了
return 0;
}
}
}
return 0;
}
T3 Match
哇塞,是紫题也
考点:栈+搜索+哈希优化
首先,我们需要判断无解情况
我们可以将每一个元素与栈顶元素比较:当当前元素和栈顶元素相同,弹出栈顶元素;否则,进行压入操作
如果有解,既可以一一对应,那么栈里不应该有任何残留元素,若有残留,则说明无解
接下来,就是 DFS
我们每当搜到一组可以进行匹配的字母时,进行标记,设当前匹配的两个字母靠左的下标为 i i i ,靠右的下标为 j j j ,那么,我们下一步需要搜索的就是两个字母的中间部分( [ i + 1 , j − 1 ] [\ i+1,j-1\ ] [ i+1,j−1 ] )以及靠右部分( [ j + 1 , r ] [\ j+1,r\ ] [ j+1,r ])
代码如下:
//一开始,我们将 1 和 n 代入进行搜索
void dfs(int l,int r){
if(l>r){ //退出条件
return ;
}
int tot;
for(int i=r;i>=l;i--){ //依次枚举
if(a[l]==a[i]){ //找到了可以进行匹配的
tot=i; //标记
break;
}
}
ans[l]='(',ans[r]=')'; //处理答案
dfs(l+1,tot-1),dfs(tot+1,r); //上文已提
}
可惜,这是错的
考虑这样一组样例: a a b a a b aabaab aabaab
按照我们的搜索方法,第一次,我们会让 1 1 1 号元素和 5 5 5 号元素进行配对,但实际上,这组样例的唯一解是让 1 1 1 号元素和 2 2 2 号元素进行配对
所以,采用哈希优化
对于 l l l 号元素和 i i i 号元素,如果我们可以进行配对,则应该满足如下要求:
- a [ l ] = a [ i ] a[\ l\ ]=a[\ i\ ] a[ l ]=a[ i ]
- i i i 号元素尚未匹配
- 区间 [ l + 1 , i − 1 ] [\ l+1,i-1\ ] [ l+1,i−1 ] 内的元素是匹配的
- 区间 [ i + 1 , r ] [\ i+1,r\ ] [ i+1,r ] 内的元素是匹配的
第 1 1 1 条和第 2 2 2 条是很容易求解的,考虑解决第 3 3 3 条和第 4 4 4 条
我们可以通过一个哈希来解决
还记得之前的栈吗?
我们只需要在进行每一次操作之后,得出该操作后栈里剩下的元素的哈希值
在每一次搜索中,我们只需要两个条件即可分别对应上述的第 3 3 3 条要求和第 4 4 4 条要求: h a s h [ l ] = h a s h [ i − 1 ] hash[\ l\ ]=hash[\ i-1\ ] hash[ l ]=hash[ i−1 ] 以及 h a s h [ i ] = h a s h [ r ] hash[\ i\ ]=hash[\ r\ ] hash[ i ]=hash[ r ]
为什么呢?
当我们对第 l l l 号元素进行了相应的操作之后,或许会剩下一些杂七杂八的元素,而经过若干处理后,直到完成了第 i − 1 i-1 i−1 号元素的处理后,剩下的还是这些元素!
这说明什么?
说明区间 [ l + 1 , i − 1 ] [\ l+1,i-1\ ] [ l+1,i−1 ] 里的元素是可以匹配的
同理:当 h a s h [ i ] = h a s h [ r ] hash[\ i\ ]=hash[\ r\ ] hash[ i ]=hash[ r ] 时,我们也就可以说明区间 [ i + 1 , r ] [\ i+1,r\ ] [ i+1,r ] 里的元素是可以匹配的
那么,完整代码如下:
#include<cstdio>
#include<cstring>
char a[100005];
int Hash[100005],s[100005],cnt,d[100005];
char ans[100005];
void dfs(int l,int r){
if(l>r){ //判断无解
return ;
}
int tot=r;
while(!(Hash[l]==Hash[tot-1]&&Hash[tot]==Hash[r])){ //一直寻找,直到满足条件
tot--;
}
ans[l]='(',ans[tot]=')'; //存贮答案
dfs(l+1,tot-1),dfs(tot+1,r); //下一步爆搜
}
int main(){
scanf("%s",a+1);
int n=strlen(a+1);
for(int i=1;i<=n;i++){ //栈操作
if(s[cnt]==a[i]){ //字符相等,选择弹出
cnt--;
}else{
s[++cnt]=a[i]; //压入
d[cnt]=d[cnt-1]*131+a[i]; //处理进制位
}
Hash[i]=d[cnt]; //处理哈希值
}
if(cnt!=0){ //判断无解情况
printf("-1");
return 0;
}
dfs(1,n); //爆搜
for(int i=1;i<=n;i++){ //输出答案
printf("%c",ans[i]);
}
return 0;
}
T4 「POI2010」珍珠项链 Beads
因为有两道珍珠项链,所以选了自己AC的一道
考点:哈希
注意这里的段是可以反转的
所以,我们可以尝试存贮两种哈希:正的和反的
初始值应该非常简单,不会见代码
考虑其中一段的哈希值:
区间 [ i , i + j − 1 ] [\ i,i+j-1\ ] [ i,i+j−1 ] 的正哈希值是很简单的,即 h a s h [ i + j − 1 ] − h a s h [ i ] × P o w [ i ] hash[\ i+j-1\ ]-hash[\ i\ ]\times Pow[\ i\ ] hash[ i+j−1 ]−hash[ i ]×Pow[ i ]
那反哈希值呢?
倒过来看一看
其实答案一目了然: h a s h 1 [ j ] − h a s h [ i + j ] × P o w [ i ] hash1[\ j\ ]-hash[\ i+j\ ]\times Pow[\ i\ ] hash1[ j ]−hash[ i+j ]×Pow[ i ]
将上面得到的两个哈希值怼入 f l a g flag flag 的映射中,如果可行, s u m sum sum (即当前长度所能得到的段的数量)自加,最后进行比较,统计,输出
具体详情参考代码
#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[200005];
int Hash[200005],Hash1[200005],Pow[200005];
map<int,bool> flag;
int num[200005],print[200005];
const int N=133333331;
int read(){
int b=0;
int a=1;
char ch=getchar();
while(!(ch>='0'&&ch<='9')){
if(ch=='-'){
a=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
b=b*10+ch-'0';
ch=getchar();
}
return a*b;
} //快读
int main(){
int n,maxn=-2147483647,tot=0;
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
Pow[0]=1;
for(int i=1;i<=n;i++){
Hash[i]=Hash[i-1]*N+a[i]; //求正哈希
Pow[i]=Pow[i-1]*N;
}
for(int i=n;i>=1;i--){
Hash1[i]=Hash1[i+1]*N+a[i]; //求反哈希
}
for(int i=1;i<=n;i++){
int ans=0;
flag.clear();
for(int j=1;j<=n-i+1;j+=i){
int x=Hash[j+i-1]-Hash[j-1]*Pow[i],y=Hash1[j]-Hash1[i+j]*Pow[i]; //上文已提,求正反哈希值
if(flag[x]==0&&flag[y]==0){ //可以塞入 flag 中
flag[x]=flag[y]=1; //塞
ans++; //答案自加
}
}
num[i]=ans; //对应长度的答案进行统计
maxn=max(maxn,ans); //与最终答案进行对比
}
for(int i=1;i<=n;i++){
if(num[i]==maxn){ //如果当前长度所得段的数量与最大值一样
tot++; //数量自加
print[tot]=i; //统计在输出数组里
}
}
printf("%d %d\n",maxn,tot); //求得 1,2 小问
for(int i=1;i<=tot;i++){
printf("%d ",print[i]); //输出输出数组
}
return 0;
}
附录
下图是关于蒟蒻患有青少年懒惰综合征的证明
[ HF医院病情证明 患者姓名 包博文 患者性别 男 患者病情 青少年懒惰综合征晚期 病情详情 写的帖子非常水 治疗药物 暂无 医生建议 没救了没救了 ] \begin{bmatrix}\ &\ &\text{HF医院病情证明}&\ &\ \\\text{患者姓名}&\text{包博文}&\ &\text{患者性别}&\text{男}\\\text{患者病情}&\text{青少年懒惰综合征晚期}&\ &\text{病情详情}&\text{写的帖子非常水}\\\text{治疗药物}&\text{暂无}&\ &\text{医生建议}&\text{没救了没救了}\end{bmatrix} ⎣ ⎡ 患者姓名患者病情治疗药物 包博文青少年懒惰综合征晚期暂无HF医院病情证明 患者性别病情详情医生建议 男写的帖子非常水没救了没救了⎦ ⎤