原题链接:http://210.44.14.31/problem/show/1078
relation存的是 该结点相对于父结点的关系。
0代表同类 1代表父吃子2代表子吃父
理解三个公式:
①压缩关系公式:
(relation[儿子]+relation[父亲])%3== 儿子和爷爷的关系
②合并根节点关系公式:
((d-1)+3-ani[儿子2].relation+ani[儿子1].relation) % 3==儿子1的父亲与儿子2的父亲的关系
③在根节点相同、d==2的情况下,判定当前条件的关系是否与已知的关系冲突:
(3 - relation[儿子1] + relation[儿子2]) % 3 ==1
穷举法均可证明三个公式的正确性。
(证明本写完一遍了,可发表后空格全乱了)
特别注意:
①路径压缩时应保证父结点的正确性,可递归调用。
②cin超时
代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 50000 + 10;
int n, k;
int relation[N], pre[N]; //记录与父节点的关系和父结点
int find(int animal) //在之前我写过的非递归调用的路径压缩会导致利用了不正确的父结点而导致错误
{
if (animal == pre[animal])
return animal;
int temp = pre[animal];
pre[animal] = find(temp);
relation[animal] = (relation[animal] + relation[temp]) % 3;//公式①
return pre[animal];
}
void combine(int X,int Y, int x, int y, int d)//合并根节点
{
pre[Y] = X;
relation[Y] = ((d - 1) + 3 - relation[y] + relation[x]) % 3;//公式②
}
void init(int n)//初始化
{
for (int i = 1; i <= n; i++)
{
relation[i] = 0;
pre[i] = i;
}
}
int main()
{
int ans = 0;
scanf("%d%d", &n, &k);
init(n);
while (k--)
{
int d, x, y;
scanf("%d%d%d", &d, &x, &y);
if ((x > n || y > n) || (d == 2 && x == y)) //①超范围 ②自己吃自己
ans++;
else
{
int X = find(x);
int Y = find(y);
if (X != Y) //不同根结点时,合并
{
combine(X, Y, x, y, d);
}
else
{
if (d == 1 && relation[x] != relation[y])//若 x与y同类,则两者与同一根结点的关系应该 一致
ans++;
else if (d == 2 && (3 - relation[x] + relation[y]) % 3 != 1) //公式③
ans++;
}
}
}
cout << ans << endl;
return 0;
}