//1182 食物链 利用向量偏移的并查集
//
//每个集合都有三类动物,用rank表示,0——同类;1——食物;2——天敌,初始时都以自己为根节点,且rank为0
//只需对每一句话,判断x,y是否同一集合内,如果是就判断语句真假,否则就合并集合
//根据公式知道x-y=d-1,主要想出几个推导公式:
//1: rank[x]=(rank[x]+rank[temp])%3
// 这个最好设一两个例子验证一下,由于每次都是两集合的根合并,且只有根更改rank,所以每个x的rank值只会与
// 它的father有关(只有一次father更改,就是跟合并时),然后结合向量偏移得到。
// 比如a-b = 1 b-c = 1 那么a-c等于2
//三个公式都与向量偏移计算得来,看下面的详解
//附1:
//向量偏移解说
/*
> 什么叫做向量的思维模式?
> Orz Orz
我的理解是,对于集合里的任意两个元素a,b而言,它们之间必定存在着某种联系,
因为并查集中的元素均是有联系的,否则也不会被合并到当前集合中。那么我们
就把这2个元素之间的关系量转化为一个偏移量,以食物链的关系而言,不妨假设
a->b 偏移量0时 a和b同类
a->b 偏移量1时 a吃b
a->b 偏移量2时 a被b吃,也就是b吃a
有了这些基础,我们就可以在并查集中完成任意两个元素之间的关系转换了。
不妨继续假设,a的当前集合根节点aa,b的当前集合根节点bb,a->b的偏移值为d-1(题中给出的询问已知条件)
(1)如果aa和bb不相同,那么我们把bb合并到aa上,并且更新delta[bb]值(delta[i]表示i的当前集合根节点到i的偏移量)
此时 aa->bb = aa->a + a->b + b->bb,可能这一步就是所谓向量思维模式吧
上式进一步转化为:aa->bb = (delta[a]+d-1+3-delta[b])%3 = delta[bb],(模3是保证偏移量取值始终在[0,2]间)
(2)如果aa和bb相同,那么我们就验证a->b之间的偏移量是否与题中给出的d-1一致
此时 a->b = a->aa + aa->b = a->aa + bb->b,
上式进一步转化为:a->b = (3-delta[a]+delta[b])%3,
若一致则为真,否则为假。
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 50010
int father[MAXN];
int rank[MAXN];
int ans=0;
int n,m;
void init(int x)
{
int i;
for(i=1;i<=n;i++)
{
father[i]=i;
rank[i]=0;
}
}
int find(int x)
{
int t;
if(father[x]==x)
return x;
t=find(father[x]);
rank[x]=(rank[x]+rank[father[x]])%3;
father[x]=t;
return t;
}
void union_set(int x,int y,int d)
{
if(x>n||y>n)
{
ans++;
return ;
}
int a=find(x);
int b=find(y);
if(a==b)
{
if((rank[x]-rank[y]+3)%3!=d)
{
ans++;
return ;
}
}
father[a]=b;
rank[a]=(rank[y]+d+3-rank[x])%3;
}
int main()
{
int x,y,d;
int i;
scanf("%d%d",&n,&m);
init(n);
while(m--)
{
scanf("%d%d%d",&d,&x,&y);
union_set(x,y,d-1);
}
printf("%d\n",ans);
return 0;
}