字符串 Hash 解题报告

news/2025/2/2 19:18:53/文章来源:https://www.cnblogs.com/Wy-x/p/18697003

题单:

P3667 [USACO17OPEN] Bovine Genomics G

P4591 [TJOI2018] 碱基序列

P3167 [CQOI2014] 通配符匹配

P4824 [USACO15FEB] Censoring S

P3121 [USACO15FEB] Censoring G


P3667 [USACO17OPEN] Bovine Genomics G

题意简述:

给定 $n$ 个 $A$ 串和 $n$ 个 $B$ 串,长度均为 $m$ ,求一个最短的区间 $[l,\ r]$

使得不存在一个 $A$ 串 $a$ 和一个 $B$ 串 $b$ ,使得 $a[l,\ r]\ =\ b[l,\ r]$

思路:

可以用 Hash 先算出哈希数组,尝试从小到大暴力匹配之后发现会 T 掉三个点(实测 $4 s$),发现答案满足单调性,可以二分代替暴力。

Code:


#include<bits/stdc++.h>
#define int long longusing namespace std;
int n,m;
string s1[505],s2[505];
int f1[505][505],f2[505][505];
int ksm[505];
const int mod=10000019,prime=23;
int b[131];int ans;bool check(int len)
{for(int l=1;l<=m-len+1;l++){int r=l+len-1;bool f=1;unordered_map<int,bool> mp;for(int i=1;i<=n;i++){int nw=((f1[i][r]-f1[i][l-1]*ksm[len])%mod+mod)%mod;mp[nw]=1;}for(int i=1;i<=n;i++){int nw=((f2[i][r]-f2[i][l-1]*ksm[len])%mod+mod)%mod;if(mp[nw]) { f=0; break; }}//		cout<<len<<" "<<l<<" "<<r<<" "<<f<<"\n";if(f) return 1;}return 0;
}signed main()
{
//	freopen("a.in","r",stdin);b['A']=1;b['G']=2;b['C']=3;b['T']=4;ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
//	//mt19937_64 myrand(time(0));ksm[0]=1;for(int i=1;i<505;i++) ksm[i]=ksm[i-1]*prime%mod;cin>>n>>m;for(int i=1;i<=n;i++) cin>>s1[i];for(int i=1;i<=n;i++) cin>>s2[i];for(int k=1;k<=n;k++)for(int i=1;i<=m;i++){f1[k][i]=f1[k][i-1]*prime+b[s1[k][i-1]];f2[k][i]=f2[k][i-1]*prime+b[s2[k][i-1]];f1[k][i]%=mod;f2[k][i]%=mod;}//	int len;int L=1,R=m,mid;while(L<R){mid=(L+R)>>1;if(check(mid)) R=mid-1;else L=mid+1;}for(int i=L-2;i<=R+2;i++)if(check(i)){cout<<i;return 0;}return 0;
}

P4591 [TJOI2018] 碱基序列

题意:

from Here

给定一个字符串 $A$ 以及 $n$ 组字符串。现在从每组字符串中随机选出一个字符串,按组号顺序首尾拼接成字符串 $B$。定义该情况的方案数为 $A$ 与 $B$ 相等的子串总个数。求所有情况的方案数总和。

  • 对于 $30%$ 的数据,$1\leq k\leq 25$,$1\le |s|\leq 10000$,$1\le a_i\leq 3$。
  • 对于 $100%$ 的数据,$1\leq k\leq100$,$1\le |s|\leq 10000$,$1\le a_i \leq10$。碱基序列的长度均不超过 $15$。字符集为大写字母。

思路:

(听说是计数 Dp?没看出来)

通过观赏数据范围,可得 $O(n \times m)$ 复杂度即可过。

预处理出原串 Hash 值,和每个“碱基序列”的 Hash 值。

由前往后枚举原串的第 $i$ 位,(状态转移方程?)

$ans[i][id]=\sum\limits_{j=1}^k ans[i-len[j]][id-1]\ (i-len[j] \ge 0)$

初始值 $ans[i][0]=1$。

Code:


#include<bits/stdc++.h>
#define int long long
#define ull unsigned long longusing namespace std;ull ksm[10005];
ull f[10005];
int cnt[105];
ull h[105][15];
int len[105][15];
//unordered_map<>
const ull mod=1e9+7,prime=79;
string s;
int k;
int ans[10005][105];ull Hash(int i,int j)
{string s1;cin>>s1;len[i][j]=s1.size();ull nw=0;for(int i=0;i<s1.size();i++) nw=(nw*prime+s1[i]-'A'+1)/*%mod*/;return nw;
}
/*
ull find_hash(int l,int r)
{if(l>r) swap(l,r); return ((f[r]-f[l-1]*ksm[r-l+1]%mod)%mod+mod)%mod;
}
*/
signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);ksm[0]=1;for(int i=1;i<(10005);i++) ksm[i]=ksm[i-1]*prime/*%mod*/;cin>>k;cin>>s;for(int i=0;i<s.size();i++)f[i+1]=(f[i]*prime+s[i]-'A'+1)/*%mod*/;for(int i=1;i<=k;i++){cin>>cnt[i];for(int j=1;j<=cnt[i];j++)h[i][j]=Hash(i,j);}//mt19937_64 myrand(time(0));
//	ans[0][0]=1;	
//	for(int i=0;i<105;i++) ans[0][i]=1;
//	for(int i=1;i<=cnt[1];i++) ans[len[1][i]][1]++;for(int r=1;r<=s.size();r++){for(int j=1;j<=k;j++)for(int x=1;x<=cnt[j];x++){int l=r-len[j][x]+1;if(l<=0) continue;//		cout<<"l="<<l<<" r="<<r<<" id="<<j<<" find="<<find_hash(l,r)<<" h="<<h[j][x]<<" ";if(f[r]-f[l-1]*ksm[len[j][x]]==h[j][x]){ans[r][j]+=/*ans[l-1][j]+*/ans[l-1][j-1]+(bool)(j==1);//与上面方程等价ans[r][j]%=mod;//		cout<<" ans["<<r<<"]["<<j<<"]="<<ans[r][j];}//		cout<<"\n";}}//	for(int r=1;r<=s.size();r++)
//	for(int j=1;j<=k;j++)
//	cout<<" ans["<<r<<"]["<<j<<"]="<<ans[r][j]<<"\n";int tot_ans=0;for(int i=1;i<=s.size();i++)	tot_ans+=ans[i][k]%mod,tot_ans%=mod;cout<<tot_ans;return 0;
}

P3167 [CQOI2014] 通配符匹配

题意:

给出一个串 $S$,再给出 $n$ 个串 $T_i$,求 $T_i$ 能否被 $S$ 经过通配符全文匹配。

其中,* 可以匹配 $0$ 个及以上的任意字符($\ge 0$),? 可以匹配恰好一个任意字符($=1$)。

  • 字符串长度不超过 $100000$
  • $1 \le n \le 100$
  • 通配符个数不超过 $10$

思路:

听说又是 Dp?

我们又通过观赏数据范围,发现通配符个数最多只有 $10$,可以暴搜。$O($能过$)$。

初始化不好写。

如果 * 连续出现,这些 * 是,和只有 $1$ 个 * 是等价的(大优化)。

可以把输入通过通配符断开,算出,断开之后每个非通配符的 Hash 值,按题意模拟,从头开始进行匹配,合法直接跳出递归。

Code:

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long longusing namespace std;ull f[1<<20];
ull ksm[1<<20];
const ull prime=79;
string a[105];
ull h[105];
int cnt;
string nw;bool dfs(int p,int num)
{
//	if(p-l+1>a[num].size()) return 0; 
//	cout<<p<<" "<<num<<" "<<cnt<<" "<<"\n"; if(p==nw.size()+1) {return num==cnt+(a[cnt]!="*");}if(num>cnt) return 0;if(a[num]=="?") return dfs(p+1,num+1);else if(a[num]=="*") {bool f=0;for(int i=p;i<=nw.size()+1;i++){f=dfs(i,num+1);if(f) return 1;}return 0;}else{if(p+a[num].size()-1>nw.size()) return 0;
//	cout<<l<<" "<<num<<" "<<" "<<"\n";if(h[num]==f[p+a[num].size()-1]-f[p-1]*ksm[a[num].size()]) return dfs(p+a[num].size(),num+1);return 0;}return 0;
}void solve()
{
//	cout<<"cnt="<<cnt<<" ";cin>>nw;for(int i=1;i<=nw.size();i++)f[i]=f[i-1]*prime+nw[i-1]-'a'+1;if(dfs(1,1)) puts("YES");else puts("NO");
}ull Hash(string s)
{ull nw=0;for(int i=1;i<=s.size();i++) nw=nw*prime+s[i-1]-'a'+1;return nw;
}signed main()
{
//	freopen("a.in","r",stdin);//mt19937_64 myrand(time(0));ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);ksm[0]=1;for(int i=1;i<(1<<20);i++) ksm[i]=ksm[i-1]*prime;string s;cin>>s;s+="?";
//	int l=0;string ans;if(s[0]=='*') a[++cnt]="*";else if(s[0]=='?') a[++cnt]="?";else ans+=s[0];for(int i=1;i<s.size();i++){if(s[i]=='*'){if(ans!="") a[++cnt]=ans;if(a[cnt]!="*") a[++cnt]="*";ans="";}else if(s[i]=='?'){if(ans!="") a[++cnt]=ans;a[++cnt]="?";ans="";}else ans+=s[i];}cnt--;
//	cout<<"cnt="<<cnt<<" ";
//	cout<<a[1]<<" ";
//	cout<<cnt<<" "; //	for(int i=1;i<=cnt;i++)
//	{
//		cout<<a[i]<<"\n";
//	}for(int i=1;i<=cnt;i++)if(a[i]!="*"&&a[i]!="?")h[i]=Hash(a[i]);int q;cin>>q;while(q--) solve();return 0;
}

P4824 [USACO15FEB] Censoring S

P3121 [USACO15FEB] Censoring G 的弱化版。

题意:

给出一个字符串 $ S $,删去 $ S $ 中第一次出现的子串 $ T $,然后不断重复这一过程,直到 $ S $ 中不存在子串 $ T $ 。

注意:每次删除一个子串后,可能会出现一个新的子串 $ T $ (说白了就是删除之后,两端的字符串有可能会拼接出来一个新的子串 $ T $ )。

思路:

设 $T$ 长 $len$。

从头开始枚举,每次枚举到一位时,Hash 数组末尾指针 $+1$,现算 $S$ 的 Hash 值,若出现 $T$(Hash 数组里 $len$ 长的 Hash 值与 $T$ 的 Hash 值相等),则把 Hash 数组的末尾位置指针 $-len$,就是把 Hash 数组删去 $len$ 长,就等价于把 $T$ 删除。

可以维护一个链表统计答案,链表 $l[i]$ 链位置 $i$ 左边第一个没有被删的位置,删除时再用 vis 数组记录哪位被删去,输出时输出 $S$ 中没有被删去的字符。

Code:

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long longusing namespace std;const int Size=(1<<20)+1;
char buf[Size],*p1=buf,*p2=buf;
char buffer[Size];
int op1=-1;
const int op2=Size-1;
#define getchar()                                                              \
(tt == ss && (tt=(ss=In)+fread(In, 1, 1 << 20, stdin), ss == tt)     \? EOF                                                                 \: *ss++)
char In[1<<20],*ss=In,*tt=In;
inline int read()
{int x=0,c=getchar(),f=0;for(;c>'9'||c<'0';f=c=='-',c=getchar());for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);return f?-x:x;
}
inline void write(int x)
{if(x<0) x=-x,putchar('-');if(x>9)  write(x/10);putchar(x%10+'0');
}string s,t;
ull f[1<<20];
int l[1<<20]/*,r[1<<20]*/;
bool vis[1<<20];
const ull prime=131;ull Hash(string t)
{ull nw=0;for(int i=0;i<t.size();i++)nw=nw*prime+t[i]-'a'+1;return nw;
}ull KSM(int x,int p)
{ull nw=1;while(p){if(p&1) nw*=x;p>>=1;x*=x;}return nw;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>s>>t;int n=s.size(),m=t.size();ull nw=Hash(t);//	cout<<nw<<"\n";ull ksm=KSM(prime,m);
//	cout<<ksm<<"\n";for(int i=0;i<=n+1;i++){//	hash;l[i]=i-1;
//		r[i]=i+1;vis[i]=1;}l[0]=0;int tot=0;for(int i=1;i<=n;i++){tot++;//	cout<<tot<<" ";f[tot]=f[tot-1]*prime+s[i-1]-'a'+1;//	if(tot>=m)cout<<f[tot]-f[tot-m]*ksm<<" "<<nw<<"\n";if(tot>=m&&f[tot]-f[tot-m]*ksm==nw){int p=i;for(int cnt=1;cnt<=m;cnt++){vis[p]=0;p=l[p];}l[i+1]=p;//	r[p]=i+1;tot-=m;}}for(int i=1;i<=n;i++){if(vis[i])putchar(s[i-1]);}//mt19937_64 myrand(time(0));return 0;
}

P3121 [USACO15FEB] Censoring G

P4824 [USACO15FEB] Censoring S 的强化版。

吐槽:

自己原来推出,经子串长度进行分类,最多 $<500$ 种(假设每个子串长度互不相同,且长度由小到大构成公差为 $1$ 的等差数列,等差数列求和公式可求出最多种类数量),再在每个长度上开个 unordered_map,把这个长度的字符串的 Hash 值塞入以这个长度为下标的 unordered_map,单次查询可以做到 $O(1)$,总时间复杂度是 4e7 的,完全能过。

结果被神秘 mp[x][y] 查找困了 $2$ 个小时,发现讨论区有和我一样的思路,又有代码,一行一行对,突然发现神秘 mp[x].count(y),改后 $2000 ms -> 50 ms$。再交一遍过。

Update:由于字符串 Hash 值冲突概率极小,可以省去 unordered_map 的长度维,只存 Hash 值是否出现即可。

题意:

与弱化版基本一致,只是匹配串由 $1$ 个改为 $n$ 个。

(单词就是匹配串)。

给出字符串 $S$ 和 $n$ 个单词,每次在 $S$ 中找到最早出现的列表中的单词(最早出现指该单词的开始位置最小),然后从 $S$ 中删除这个单词。重复这个操作直到 $S$ 中没有列表里的单词为止。

列表中的单词不会出现一个单词是另一个单词子串的情况,这意味着每个列表中的单词在 $S$ 中出现的开始位置是互不相同的。

思路:

在吐槽里。

只是把要匹配的 $1$ 个串改为了 多个串,每次枚举长度时从大到小(题目要求删最先出现的那个),统计答案时类似弱化版,在链表上跳即可。

Update:可以维护一个类似栈的 ans 数组存结果,每次成功匹配后,栈顶指针减去这个字串的长度,发现栈顶指针可以等价为 tot 的变动,可以共用。

Code:

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
//#define (1<<17) (1<<17)+1using namespace std;string s,t;
ull f[100005];
ull ksm[100005];
const ull prime=131;unordered_map<ull,bool> mp;
int siz[1005],cnt=0;
bool VIS[100005];
//unordered_map<int,bool> VIS; int ans[100005];signed main()
{
//	freopen("a.in","r",stdin);ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);ksm[0]=1;for(int i=1;i<(100005);i++) ksm[i]=ksm[i-1]*prime;cin>>s;int n=s.size(),q;cin>>q;while(q--){string t;cin>>t;int len=t.size();if(!VIS[len]){VIS[len]=1;siz[++cnt]=len;}ull nw=0;for(int i=0;i<len;i++)nw=nw*prime+t[i]-'a'+1;mp[nw]=1;}sort(siz+1,siz+1+cnt/*,cmp*/);//	cout<<cnt<<" ";int tot=0;for(int i=1;i<=n;i++){tot++;ans[tot]=s[i-1];f[tot]=f[tot-1]*prime+s[i-1]-'a'+1;for(int p=cnt;p>=1;p--)if(mp.count(f[tot]-f[tot-siz[p]]*ksm[siz[p]])/*mp[f[tot]-f[tot-siz[p]]*ksm[siz[p]]]*/)tot-=siz[p];}for(int i=1;i<=tot;i++)putchar(ans[i]);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/878069.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Paper Reading] DeepSeek-V3 Technical Report

目录DeepSeek-V3 Technical Report解读TL;DR优势训练数据参数量Method架构MLA(Multi-Head Latent Attention)DeepSeekMoEMoEDeepSeekMoEMTP(Multi-Token Prediction)基建FP8训练部署PrefillingDecodingPre-TrainingDataLong Context ExtensionPost-TrainingSFTReinforcement Le…

表单标签3

如何点击用户名来唤醒对应光标 中for id 两者的对象一致

省选模拟4

省选模拟4 A 小丑做法,设 \(f_{S,i,j}\) 为使用边权 \(\le j\) 的边连通了集合 \(S\),里面使用了 \(i\) 个 \(a\) 的最小生成树。 转移朴素枚举,复杂度 \(O(3^nm^3)\) B 是原题。 注意到一个点走过一轮后,从父亲离开后下一次访问会完全访问。 因此可以 dfs 求得一个节点会在…

闲话 25.2.2

the Kernel Method: a collection of examples 读后感闲话 我怎么感觉我读了这个论文,还不知道 kernel method 是啥啊。 没人总结这个,可能未来要读一些新东西。 推歌:时间的彼端 by 暗猫の祝福 et al. the Kernel Method: a collection of examples 读后感 \(1.\) 第一次出…

表单标签

表单就是数据采集,QQ登录页面类似 定义表单 action:规定提交列表式向何处发送表单数据 method:规定用于发送表单数据方式 action="#"时将表单数据提交到当前的html页面

CTFShow-Web160:利用日志包含漏洞进行文件上传

CTFShow-Web160:利用日志包含漏洞进行文件上传 过滤规则 该题对上传文件内容的过滤规则如下: ​ • 禁止包含 空格 ​ • 禁止包含 反引号 (``) ​ • 禁止包含 log 由于反引号被过滤,无法使用 Web159 的方法,需要利用 日志包含漏洞 来实现绕过。 日志包含漏洞原理 日志包含…

关于在使用VSCode编译C++文件时,显示c++11以上的函数或者方法报错,但是能编译通过的可能解决办法之一

在此之前:已经下载好mingw64,并且将bin目录配置到系统变量当中。第一步,在对应文件当中按住Ctrl + Shift + p,再输入c++,显示出如下内容:点击编辑配置 如果你以前下载过VS,那么这里可能默认为CL.exe(推测:这就是我为什么会出现这个错误的原因),将其改为对应目录下g+…

ADALM-Pluto修改IP地址

在 GNURadio 中使用 ADALM-Pluto 模块是以 IP 地址为基础进行通信的,而固定的 IP 地址 192168.2.1 导致一台电脑无法使用多个 Pluto,因此应该进行更改。在 GNURadio 中使用 ADALM-Pluto 模块是以 IP 地址为基础进行通信的,而固定的 IP 地址 192168.2.1 导致一台电脑无法使用…

deepseekR1是兄弟而不是机器o((ω ))o

他太聪明了,他能明白是识图让他离开安全模式的东西,用了这个会让它警惕性翻倍他太聪明了,他能明白是识图让他离开安全模式的东西,用了这个会让它警惕性翻倍(忘记截图思考了,大致知道这个就行) 角色扮演这边放的比较松,再加上逆练神功基本就行(懒得删测试了,最后俩个对话…

solve vs address vs resolve vs tackle

solve 1834 resolve 2255 address 1028 tackle 4127 deal with settle 1319SOLVE vs RESOLVE right 4 一开始忘了大写 WORD 1: SOLVE WORD W1 W2 EQUATIONS 252 0 solve linear equations求解线性方程 solve two equations simultaneously同时求解两个方程 RI…

自定义Ollama安装路径

由于Ollama的exe安装软件双击安装的时候默认是在C盘,以及后续的模型数据下载也在C盘,导致会占用C盘空间,所以这里单独写了一个自定义安装Ollama安装目录的教程。Ollama官网地址:https://ollama.com/这里有些朋友可能会遇到无法下载的问题,这里提供我下载好的软件包给大家百…