P9013 [USACO23JAN] Find and Replace S
你说得对,但是图论是初学者的弱点。
首先每个字母只能对应一个字母,这应该没有异议吧。
通过对示例进行模拟,我们不难发现,这道题的难点在于:环该如何处理。
情况比较复杂需要仔细分析,但是由于每个点出度均为 \(1\) ,所以分析起来并不难。
假设每一个字母都是一个点建图,分两种情况讨论。
第一种情况,如果字母没有全部选择完。
如果这部分形成树状,那么每个点需要改变一次,除非这个点连接的是自己。
所用次数为所需改变的树上节点的个数,若连接自己则不用加一。
例如:
abc
bbc
如果形成环状,那么每一个环中都需要有一个点先变成某一个不在图中,其他点形成树状,将这个树处理后,将那个点变为需要变的点即可。
由于如果你本来就不需要变,出度为 \(1\) 则不可能和其他点成为环,所以不考虑有自连的情况。
那么除了环内的点加一以外长度额外加一。
例如:
ABC
BCA
最特殊的形式,叫基环树,就是一个环上插一颗树,大概就是这样:
ABCDEF
BCAADE
那么此时就有一个很好的处理方式了。
将 \(C\) 变为 \(D\) ,把环拆成树,之后再将新树 \(ABC\) 解决了,再把 \(D\) 变为 \(A\) ,最后解决掉树 \(DEF\) 即可。
那么就和树所耗的次数相同,都是每有一个点就改变一次。
而这种同样没有自连情况。
那么我们不难发现在此情况下,大部分都用一次,而唯独在树上自恋时减一和有普通环时额外加一。
那么还有第二种情况:所有字母都用完了。
需要变成其他字母的只有环,所以环的处理会受到影响。
如果全都是环,那么肯定不行了。
但是如果产生了基环树或者有自连的树(非单个点),那么就会有空位产生,就能够解决。
这样就讨论完了全部情况,使用一个并查集查找环再具体分析处理即可。
代码:
#include<bits/stdc++.h>
using namespace std;
char s[100005];
int T,a[100005],b[100005],c[100005],t[100005],fa[100005];
bool vis[100005];
int uid(char c){
if(c<='Z')return c-'A'+1;
else return c-'a'+27;
}
int find(int x){
if(x==fa[x])return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return;
fa[x]=y;
}
signed main(){
cin>>T;
while(T--){
memset(c,0,sizeof(c));
memset(t,0,sizeof(t));
memset(vis,0,sizeof(vis));
for(int i=1;i<=52;i++)fa[i]=i;
cin>>s+1;
int len=strlen(s+1);
for(int i=1;i<=len;i++)a[i]=uid(s[i]);
cin>>s+1;
for(int i=1;i<=len;i++)b[i]=uid(s[i]);
bool f=0;
int cnt=0;
for(int i=1;i<=len;i++){
if(!c[a[i]])c[a[i]]=b[i],t[b[i]]++,cnt++;
else if(c[a[i]]!=b[i])f=1;
}
if(f){
puts("-1");
continue;
}
for(int i=1;i<=52;i++)if(c[i])merge(i,c[i]);
bool fl=0;
int ans=0;
for(int i=1;i<=52;i++)if(c[i]&&t[c[i]]>1)fl=1,vis[find(i)]=1;
for(int i=1;i<=52;i++){
if(!c[i])continue;
if(find(i)!=i)ans++;
else {
if(i==c[i])continue;
if(vis[i])ans++;
else if(!fl&&cnt==52)f=1;
else ans+=2;
}
}
if(f){
puts("-1");
continue;
}
cout<<ans<<endl;
}
return 0;
}

浙公网安备 33010602011771号