中文题,题意没什么好说的。
和这题一样,可以手写逻辑,但是需要手写个逻辑,太麻烦了。
解决本题有两种方案,一种是带权并查集,一种是完全合并,有些人觉得后者好理解,本菜鸡并不觉得,而且它还要开三倍空间,所以本篇选择带权并查集来解决本题,但是如果你想要学习这种方法,我推荐这篇博客。
带权的并查集有更加广阔的应用场景,且空间消耗小于完全合并的方案。所以完全值得你花时间学习。
注意向量的思维。
在这里,向量指的是“偏移量”,表示的是子节点与父节点之间的关系。
0表示与父节点属于同一种族;
1表示被父节点吃;
2表示吃父节点。
显然,路径上的0具备传递性。
下图我推导1和2的传递性,共推导了种传递情况,验证了路径压缩时,路径直接相加对3取模即可。
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> using namespace std; const int N = 50010; struct node { int pre; //父节点 int relation; //与父节点之间的关系 } p[N]; int find(int x) { //查找根结点 int temp; if (x == p[x].pre) return x; temp = p[x].pre; //路径压缩 p[x].pre = find(temp); p[x].relation = (p[x].relation + p[temp].relation) % 3; //关系域更新 return p[x].pre; //根结点 } int main() { int n, k; int sum = 0; //假话数量 scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) { //初始化 p[i].pre = i; p[i].relation = 0; } int ope, a, b; for (int i = 1; i <= k; ++i) { scanf("%d%d%d", &ope, &a, &b); if (a > n || b > n || ope == 2 && a == b) sum++; else { int root1 = find(a); int root2 = find(b); if (root1 != root2) { // 合并 p[root2].pre = root1; // 此时 rootx->rooty = rootx->x + x->y + y->rooty p[root2].relation = (3 + p[a].relation + (ope - 1) - p[b].relation) % 3; } else { //验证x->y之间的偏移量是否与题中给出的d-1一致 if (ope == 1 && p[a].relation != p[b].relation) sum++; else if (ope == 2 && (3 - p[a].relation + p[b].relation) % 3 != ope - 1) sum++; } } } printf("%d\n", sum); return 0; }