【NOI2001】食物链
本题在洛谷上的链接:https://www.luogu.org/problemnew/show/P2024
去年做过这道题,但当时比较meng,现在重做一遍,重写一遍博客。
虽然看起来我们需要维护某个结点和其他各个结点的关系,但实际上由于题目的特殊(吃与被吃关系构成了环),我们可以用并查集方便地维护每个结点和他父亲的关系,然后得出与其他有联系的结点的关系。
不止维护每个结点的父亲是谁,这叫做带权并查集,这里我们维护每个结点到其父亲的距离来表示与父亲的关系,0代表是同类,1是吃父亲,2是被父亲吃。
因为吃与被吃的关系构成了环,所以将距离累加再对3取模就可以得到结点与其他结点的关系了,若给出的X和Y不在同一并查集内,需要合并,让一方的最终祖先指向另一方的最终祖先,就需要根据给定的关系以及两结点到其祖先的距离求出两个祖先间该有的距离;若给定的X和Y已经在同一并查集内,根据他们到祖先的距离求出他们的距离判断即可。这里最好知道矢量的运算,个人感觉会更利于理解。
1 #include <cstdio> 2 3 inline int get_num() { 4 int num = 0; 5 char c = getchar(); 6 while (c < '0' || c > '9') c = getchar(); 7 while (c >= '0' && c <= '9') 8 num = num * 10 + c - '0', c = getchar(); 9 return num; 10 } 11 12 const int maxn = 5e4 + 5; 13 14 int fa[maxn], dist[maxn]; 15 16 int dj_find(int i) { 17 if (i == fa[i]) return i; 18 int old = fa[i]; 19 fa[i] = dj_find(old); 20 dist[i] = (dist[i] + dist[old]) % 3; 21 return fa[i]; 22 } 23 24 inline void dj_merge(int a, int b, int c) { 25 c = (c - dist[a] + dist[b] + 3) % 3; 26 a = dj_find(a), b = dj_find(b); 27 fa[a] = b, dist[a] = c; 28 } 29 30 int main() { 31 int n, k, ans = 0; 32 n = get_num(), k = get_num(); 33 for (int i = 1; i <= n; ++i) 34 fa[i] = i, dist[i] = 0; //初始化 35 //与父亲的距离为0是同类,1是吃父亲,2是被吃 36 int c, x, y; 37 for (int i = 1; i <= k; ++i) { 38 c = get_num(), x = get_num(), y = get_num(); 39 if (x > n || y > n) ++ans; 40 else if (c == 2 && x == y) ++ans; 41 else { 42 if (dj_find(x) != dj_find(y)) dj_merge(x, y, c - 1); 43 else if ((dist[x] - dist[y] + 3) % 3 != c - 1) ++ans; 44 } 45 } 46 printf("%d", ans); 47 return 0; 48 }