题目链接:
题目大意:
给出一些关系,判断矛盾的个数,先说出的未被反驳的语句我们认为是正确的
题目分析:
这种题很明显是集合的问题,但是是一个有关系的集合,所以我们可以利用带权的并查集来解决,主要做法如下:
我们定义两个数组,第一个数组就是实现并查集的fa数组,用来判断集合关系,把每个集合看做一条链,可以得到这条链的一端,我们定义它为根,因为关系满足传递性,所以我们可以通过关系的传递性推倒出当前条件下两者的关系,然后根据当前的可判断的关系来判断刚刚给出的关系是否出现矛盾
那么具体做法就是:
1.rank数组0表示当前点与根同类,1表示当前点能吃根,2表示当前点被根吃,如此定义下正好能够通过模运算,达到关系的传递性
2.首先用rank数组标记当前点与根的关系,然后我们可以利用传递性,得到任意两点间的关系((rank[a]-rank[b]) %3)
3.可以再路径压缩中维护这种关系,rank[a] = (rank[a]+rank[fa[a]] )%3, 通过将当前点到之前根的关系,加上之前根到当前根的关系,维护当前点到根的关系
4.每次添加新关系导致两个集合(两条链)连接,我们可以通过当前点的关系,反推出两个根的关系,也就是rank[ffa] = rank[a]-rank[b]+f
具体代码如下,这是带权并查集的经典题同样也是模板题,把并查集理解为一条链更容易理解
代码如下:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define MAX 50007
using namespace std;
int rank[MAX];
int fa[MAX];
int ans;
int _find ( int x )
{
//return x == fa[x]?x:fa[x]=find(x);
if ( x == fa[x] ) return x;
int temp = fa[x];
fa[x] = _find ( fa[x]);
rank[x] = (rank[x] + rank[temp])%3;
return fa[x];
}
void init ( )
{
for( int i = 0 ; i < MAX ; i++ )
fa[i] = i;
}
void _union ( int a , int b , int f )
{
int ffa = _find ( a );
int fb = _find ( b );
if ( ffa == fb ) return;
fa[ffa] = fb;
rank[ffa] = (f+rank[b]-rank[a]+3)%3;
}
int n,k,u,v,f;
bool check ( int u , int v , int f )
{
if ( u > n || v > n ) return false;
if ( f == 1 && u == v ) return false;
if ( _find(u) == _find(v))
return ((rank[u] - rank[v])%3+3)%3 == f;
else return true;
}
int main ( )
{
scanf ( "%d%d" , &n , &k );
{
ans = 0;
memset ( rank , 0 , sizeof ( rank ));
init ();
for ( int i = 0 ; i < k ; i++ )
{
scanf ( "%d%d%d" , &f , &u , &v );
f--;
if ( check ( u , v ,f ))
_union ( u , v , f );
else ans++;
}
printf ( "%d\n" , ans );
}
}