乍 一 看 像 个 贪 心 乍一看像个贪心 乍一看像个贪心
观 察 一 个 这 个 样 例 观察一个这个样例 观察一个这个样例
aab
bcc
由 于 第 一 个 a 要 变 成 b 由于第一个a要变成b 由于第一个a要变成b
第 二 个 a 要 变 成 c 第二个a要变成c 第二个a要变成c
第 三 个 b 要 变 成 c 第三个b要变成c 第三个b要变成c
按理来说需要操作三次
但 是 由 于 a − > b , b − > c 但是由于a->b,b->c 但是由于a−>b,b−>c
所 以 a − > c 的 步 骤 就 不 需 要 了 所以a->c的步骤就不需要了 所以a−>c的步骤就不需要了
也就是说,如果合并的两个字母不在一个集合中,才需要额外操作
但 是 聪 明 的 你 一 定 发 现 并 查 集 要 求 双 向 边 , 但 这 里 是 单 向 边 \color{Red}但是聪明的你一定发现并查集要求双向边,但这里是单向边 但是聪明的你一定发现并查集要求双向边,但这里是单向边
比 如 a − b , 那 么 应 该 把 a 和 b 合 并 , 但 是 这 并 不 意 味 着 b 可 以 变 成 a 啊 ! ! 比如a-b,那么应该把a和b合并,但是这并不意味着b可以变成a啊!! 比如a−b,那么应该把a和b合并,但是这并不意味着b可以变成a啊!!
幸运的是,我们的转换只存在由小字母向大字母转换,b->a是不合法的
综 上 所 诉 , 用 并 查 集 来 维 护 综上所诉,用并查集来维护 综上所诉,用并查集来维护
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int n,t,ans,pre[maxn];
char a[maxn],b[maxn];
int find(int x){
return x==pre[x]?x:pre[x]=find(pre[x]);
}
void join(int q,int w){
int qq=find(q),ww=find(w);
if( qq!=ww )
{
ans++;
pre[qq]=ww;
}
}
int main()
{
cin >> t ;
while(t--)
{
for(int i=0;i<=20;i++) pre[i]=i;
cin >> n >> (a+1) >> (b+1);
int flag=1;
ans=0;
for(int i=1;i<=n;i++) if( b[i]<a[i] ) flag=0;
if( !flag ) cout << -1 << endl;
else
{
for(int i=1;i<=n;i++)
join(a[i]-'a',b[i]-'a');
cout << ans << endl;
}
}
}
/*
aabaf
bccdf
a->b
a->c
b->c*/