P9013 [USACO23JAN] Find and Replace S题解
此题细节非常多,比赛的时候给我错的死去活来的,还好数据不强。应该也是因为不好造数据吧。
关于其他题解的错误
看了一下其他题解,感觉有一些地方可以商榷。没有兴趣的同学可以跳过这一部分。
针对 @zac2010 的题解中的代码,给出一组 hack 数据。
1
DFB
FDD
这组数据答案应该是 F 变成 B,原始字符串变成 DBB。第二步把 D 变成 F,原始字符串变成 FBB,第三步把 B 变成 D,得到答案。由此可见答案为
针对 @Demeanor_Roy 题解中的思路:
若借助字符串中不存在的点或者已经变换完成的联通块中入度为
0 的点,答案增加1 。
这里其实有问题,不能借助已经完成的连通块中入度
解题思路
首先判断无解的情况,显然第一种无解是一个字母要变换成两个或者两个以上不同的字母。这个相信大家可以轻松实现。
第二种无解的情况是,t 字符串中出现了所有的字母,但是 s 字符串和 t 字符串不相同。原因一会儿会详细解释,读者有兴趣可以在这里先思考一下。
在没有第一种无解情况的情况下,我们把这个问题转换成一个图论问题。对于
第一种情况,图中
第二种情况,带有入度的链,比如图上
第三种情况,纯环,图中
经过刚才的分析,我们发现答案等于边的条数加上纯环的个数。不过这里还有一个特殊情况,刚才说到纯环需要借一个点来拆环,这个点必须是没有在
这时候我们可以解释前文提到的第二种无解情况了。如果所有字符都在
首先因为每个字符只能变换成一种字符,而不会变换成两种以上的字符。(否则在前面情况一已经判掉了)那么
思路清楚,代码实现就很简单啦。
#include <iostream>
#include <cstring>
#include <set>
#include <queue>
using namespace std;
typedef long long ll;
const ll MAXN = 1e5 + 5;
const ll MOD = 1e9 + 7;
int T, n, to[300];//to表示i要变成什么
int in[300];//每个字符的入度
int vis[300];//vis数组记录每个字母是否已经能成功变化到它想去的字母
char s[MAXN], t[MAXN];
//从入度为0的点出发,把能到的点都标记vis
void topo() {
queue<int> q;
for (int i = 'A'; i <= 'Z'; ++i) {
if (in[i] == 0) {
q.push(i);
}
}
for (int i = 'a'; i <= 'z'; ++i) {
if (in[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 1;
int v = to[u];
if (vis[v] == 0) {
in[v] = 0;//只要能从不在环上的点走过来,强制进队
q.push(v);
}
}
}
//把从u出发同一个环上的点都标记一遍
void dfs(int u) {
vis[u] = 1;
int v = to[u];
if (vis[v] == 0) dfs(v);
}
int main() {
cin >> T;
while (T--) {
set<char> b;//集合记录所有在s中出现过的字母
memset(to, 0, sizeof to);
memset(in, 0, sizeof in);
memset(vis, 0, sizeof vis);
cin >> s >> t;
n = ::strlen(s);
//第一种无解的情况,一个字符要变成两种以上不同字符,肯定不行
int good = 1;
for (int i = 0; i < n; ++i) {
if (to[s[i]] == 0 || to[s[i]] == t[i]) {
to[s[i]] = t[i];
} else {
good = 0;
}
b.insert(t[i]);
}
if (!good) {
cout << -1 << endl;
continue;
}
//第二种无解的情况,如果所有字母都在t中出现过,那么s和t必须相等才行。
if (b.size() == 52) {
if (strcmp(s, t) == 0) {
cout << 0 << endl;
} else {
cout << -1 << endl;
}
continue;
}
//把每个原始字母向结果字母连边,建一张有向图,自环不管
memset(to, 0, sizeof to);
int ans = 0;//统计边的条数,其实就是需要的变化的数量
for (int i = 0; i < n; ++i) {
if (to[s[i]] == 0 && s[i] != t[i]) {
to[s[i]] = t[i];
in[t[i]]++;
ans++;
}
}
topo();
//此时一定有解,对于现在还没标记的点,一定是在一个纯环里面,此时对于每个环答案加1
for (int i = 'a'; i <= 'z'; ++i) {
if (vis[i] == 0) {
ans++;
dfs(i);
}
}
for (int i = 'A'; i <= 'Z'; ++i) {
if (vis[i] == 0) {
ans++;
dfs(i);
}
}
cout << ans << endl;
}
return 0;
}