Google 2020KS-RoundD D题题解
前言
感觉Google KickStart的后两题还是有一点难度的。。尤其是D题,很多时候打比赛的时候都A不掉。
但其实看题解以后发现它也没考什么很难的算法,就只是需要好好思考一下orz
害,说白了还是手生,这种稍微有点难度的题目还是得做一下
题目描述
题解
麻烦点儿的广搜应该可以做到\(O(n^2)\),但是这个题的数据范围还是过不了。
但无论是广搜还是暴力,它的思路都是将每个人的名字看成一个节点,然后有朋友关系的来连边。
实际上,每个人的名字可以看成一个集合,朋友关系可以用集合运算表示。
在集合运算中,每个元素对答案的贡献是互相独立的。类比位运算(|和&)操作。
那么在这个题中,每个字母的作用都是相互独立的。完全可以只建立字母之间的关系。
我自己就想到这一步,剩下的都是看官方题解做的。
题解的做法是,将每个字母看成节点,如果两个字母在同一个名字里,就连边。
然后跑一个floyd最短路。
查询的时候,分别枚举两个人名字里的字母c1和c2,选择c1到c2长度最短的路径作为答案。
当然输出的时候这个值要+2。
(思路是这么个思路,不知道细节跟题解是不是一样)
为什么这样建图是对的呢?考虑在这样一个图中走过了一条边代表着什么。
比如上面的例子:ABC、CDE和DEF三个人。
如果是ABC和CDE要联系,那么根本就不需要走边(因为它们有相同的字母C,最短路径是0,最后输出的答案是0+2=2)
而ABC和DEF要联系,需要走C-E这条边。这意味着它们将CDE这个人当做了中间桥梁,而CDE这个人贡献出的联系就是C-E。
则最短路径是1,加上两端的ABC和DEF,答案是3。
这也就是说,在这个图中,每次走过一条边,就说明中间的路径上多了一个人,这个人提供了一个字母的转换。
最短路径求出来就是中间最少经过几个人,然后加上两边的人就是答案。
代码
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int T, n, q;
char name[50010][30];
int f[30][30];
void addPath(char* str) {
int len = strlen(str);
for (int i = 0; i < len; i ++)
for (int j = 0; j < len; j ++)
f[str[i] - 'A' + 1][str[j] - 'A' + 1] = 1;
}
int getAnswer(char* s1, char* s2) {
int l1 = strlen(s1), l2 = strlen(s2);
int ans = n + 1;
for (int i = 0; i < l1; i ++)
for (int j = 0; j < l2; j ++)
ans = min(ans, f[s1[i] - 'A' + 1][s2[j] - 'A' + 1]);
if (ans == n + 1) ans = -1;
else ans += 2;
return ans;
}
int main()
{
scanf("%d", &T);
for (int wer = 1; wer <= T; wer ++) {
scanf("%d%d", &n, &q);
memset(f, 0x3f, sizeof(f));
for (int i = 1; i <= n; i ++) {
scanf("%s", name[i]);
addPath(name[i]);
}
for (int i = 1; i <= 26; i ++)
f[i][i] = 0;
for (int k = 1; k <= 26; k ++)
for (int i = 1; i <= 26; i ++)
for (int j = 1; j <= 26; j ++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
printf("Case #%d:", wer);
for (int i = 1; i <= q; i ++) {
int x, y;
scanf("%d%d", &x, &y);
printf(" %d", getAnswer(name[x], name[y]));
}
printf("\n");
}
return 0;
}