[CSP-S 2025] 谐音替换题解
暴力
暴力枚举使用哪个二元组去替换这个 \(s\),我们采用系统函数 \(find\) 和 \(substr\),每次在 \(t1\) 中寻找一个 \(s1\) 替换成 \(s2\) ,判断是否会变成 \(t2\)。同时会有几个不需要处理的情况,需要特判,\(1.\) \(|t1| != |t2|\) 怎么变都不会相等,\(2.\) \(s1=s2\) 则变了等于没变。
时间复杂度是 \(O(nq|t|)\),期望得到 \(30pts\),实际得到 \(35pts\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,q;
string s1[N],s2[N];
string t1,t2;
string s;
void solve(){
while(q--){
cin>>t1>>t2;
int ans=0;
if(t1.size()!=t2.size()){
cout<<0<<"\n";
continue;
}
for(int i=1;i<=n;i++){
if(s1[i]==s2[i])continue;
int pos=t1.find(s1[i]);
while(pos!=-1){
s=t1.substr(0,pos)+s2[i]+t1.substr(pos+s2[i].size());
ans+=(s==t2);
pos=t1.find(s1[i],pos+1);
}
}
cout<<ans<<"\n";
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>s1[i]>>s2[i];
solve();
return 0;
}
寻找性质
发现对于一组可行的 \(s1,s2\),我们设其中不同的部分为 \(w1\),\(t1,t2\) 的不同部分设为 \(w2\),那样例来说,\(xabcx\ xadex\) 的不同就是 \(bc\) 和 \(de\),所以它们的 \(w\) 就是 \(bcde\)。而除去了 \(w1\) 以外的部分都是相同的,不然就不可行了。那么其一定会满足条件 \(w1=w2\),不然也不可行。因此我们可以将每一对 \(s1,s2\) 分成三个部分,分别是两个相同的部分,和中间不同的部分。对于上面的例子,三个部分就分别是 \(xa\ bcde\ x\),那么原来就不必有原来繁琐的寻找了,我们分别记为 \(p,m,b\),对于 \(t\) 的记为 \(P,M,B\)。
那么判断合法就可以变为 \(m=M\) 且 \(p\) 为 \(P\) 的后缀 \(b\) 为 \(B\) 的前缀,就满足了 \(t1\) 与 \(t2\) 不同的地方被改变,相同的地方没有改变。通过枚举 \(n\) 个 \(s\) 来判断即可,时间复杂度变为 \(O(nq)\)。期望得到 \(40pts\) 实际可以得到 \(50pts\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,q;
string p[N],m[N],b[N];
string t1,t2;
string P,M,B;
string s1[N],s2[N];
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>s1[i]>>s2[i];
if(s1[i]==s2[i])continue;
int l=0,r=s1[i].size()-1;
while(s1[i][l]==s2[i][l])l++;
while(s1[i][r]==s2[i][r])r--;
p[i]=s1[i].substr(0,l);
m[i]=s1[i].substr(l,r-l+1)+s2[i].substr(l,r-l+1);
b[i]=s1[i].substr(r+1);
}
while(q--){
cin>>t1>>t2;
if(t1.size()!=t2.size()){
cout<<0<<"\n";
continue;
}
int l=0,r=t1.size()-1;
while(t1[l]==t2[l])l++;
while(t1[r]==t2[r])r--;
P=t1.substr(0,l);
M=t1.substr(l,r-l+1)+t2.substr(l,r-l+1);
B=t1.substr(r+1);
int ans=0;
for(int i=1;i<=n;i++){
if(s1[i]==s2[i])continue;
if(m[i]!=M)continue;
if(p[i].size()>P.size() || P.substr(P.size()-p[i].size())!=p[i])continue;
if(b[i].size()>B.size() || B.substr(0,b[i].size())!=b[i])continue;
ans++;
}
cout<<ans<<"\n";
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
/*
1 1
bc de
xabcx xadex
*/
优化
发现现在的瓶颈存在于每次都需要去枚举替换的字符串,所以我们应该优化这个寻找的过程,也就是多字符串匹配。那么就有一个专门解决这个问题的算法 \(AC\) 自动机,由于需要区分开三个部分,所以在将三个部分合并的时候需要依次在中间插入 '#' 号,用来防止三个部分混淆。期望得到 \(100pts\) 实际得到 \(100pts\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200005;
int n,q;
string s;
string t1,t2;
string P,M,B;
string s1[N],s2[N];
int to[5000005][27];
int tot;
int sum[5000005];
int fail[5000005];
void insert(string f){
int u=0;
for(auto i:f){
int c=(i=='#' ? 26 : i-'a');
if(!to[u][c])to[u][c]=++tot;
u=to[u][c];
}
sum[u]++;
return;
}
void bfs(){
queue<int>q;
for(int i=0;i<=26;i++){
if(to[0][i])q.push(to[0][i]);
}
while(!q.empty()){
int u=q.front();q.pop();
sum[u]+=sum[fail[u]];
for(int i=0;i<=26;i++){
if(to[u][i]){
fail[to[u][i]]=to[fail[u]][i];
q.push(to[u][i]);
}else to[u][i]=to[fail[u]][i];
}
}
return;
}
int query(string f){
int u=0,ans=0;
for(auto i:f){
int c=(i=='#' ? 26 : i-'a');
ans+=sum[u=to[u][c]];
}
return ans;
}
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>s1[i]>>s2[i];
if(s1[i]==s2[i])continue;
int l=0,r=s1[i].size()-1;
while(s1[i][l]==s2[i][l])l++;
while(s1[i][r]==s2[i][r])r--;
s=s1[i].substr(0,l)+'#'+s1[i].substr(l,r-l+1)+'#'+s2[i].substr(l,r-l+1)+'#'+s1[i].substr(r+1);
insert(s);
}
bfs();
while(q--){
cin>>t1>>t2;
if(t1.size()!=t2.size()){
cout<<0<<"\n";
continue;
}
int l=0,r=t1.size()-1;
while(t1[l]==t2[l])l++;
while(t1[r]==t2[r])r--;
s=t1.substr(0,l)+'#'+t1.substr(l,r-l+1)+'#'+t2.substr(l,r-l+1)+'#'+t1.substr(r+1);
cout<<query(s)<<"\n";
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
/*
1 1
bc de
xabcx xadex
*/

浙公网安备 33010602011771号