本题学习到了带权并查集的神奇之处!
在网上看到了各位前辈对此题的精妙的解析,不由得感叹连连。尤其是各位前辈的认真,让我也忍不住想要把这个题用我自己的方式再融会贯通一遍。
(一)各个变量的定义
d: 表示y对x的关系。
node[i].op:定义为该节点与其父节点的关系。
0 - 表示该节点与其父节点为同类。
1 - 表示该节点被其父节点吃。
2 - 表示该节点吃其父节点。
(二)Find() 函数
int Find(int x)
{
if(x == node[x].par) return node[x].par;
int tmp = node[x].par;
node[x].par = Find(node[x].par);
node[x].op = (node[x].op + node[tmp].op) % 3; //***
return node[x].par;
}
标***处该如何理解呢?
即已知 儿子对父亲的关系,父亲对爷爷的关系,求儿子对爷爷关系。
(一共有9种情况) [该过程是A吃B,B吃C,C吃A]
i j
儿子 父亲 儿子对爷爷
0 0 (i + j)%3 = 0
0 1 (i + j)%3 = 1
0 2 (i + j)%3 = 2
1 0 (i + j)%3 = 1
1 1 (i + j)%3 = 2 //***
1 2 (i + j)%3 = 0 //***
2 0 (i + j)%3 = 2
2 1 (i + j)%3 = 0 //***
2 2 (i + j)%3 = 1 //***
对于以上四个标***处,比较难理解,我画了图。
其箭头表示箭头始端吃箭头末端。蓝色箭头表示已知,红色箭头表示后推理。
(三)Union()函数
void Union(int x,int y,int rootx, int rooty,int d)
{
node[rooty].par = rootx;
node[rooty].op = (3 - node[y].op + d-1 + node[x].op) % 3;//***
}
合并过程文字描述:
假设都是2层。把node[y],先连接到node[x]上,再把node[y]的根节点node[ry]移动到node[y]上,
最后,把node[Y]的根节点移动到node[X]的根节点上,这样算op。
可以看作把y接到x上,也就说,现在x是y的父亲,y原来的根节点ry现在是y的儿子 相加求node[ry]与node[x]的op了
再加上x对其根节点rx的关系,就是ry对rx的关系。
(四) 附代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstring>
#include<string>
#include<set>
#include<cmath>
#include<map>
#include<list>
#include<sstream>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long LL;
const int maxn = 50000 + 8;
int n,k,a,b,c,ans;
struct Node
{
int par;
int op;//0:该节点与父节点同类 1:该节点被父节点吃 2:该节点吃父节点
}node[maxn];
void Init()
{
ans = 0;
for(int i = 1; i <= n; ++i){
node[i].par = i;
node[i].op = 0;
}
}
int Find(int x)
{
if(x == node[x].par) return node[x].par;
int tmp = node[x].par;
node[x].par = Find(node[x].par);
node[x].op = (node[x].op + node[tmp].op) % 3;
return node[x].par;
}
void Union(int x,int y,int rootx, int rooty,int d)
{
node[rooty].par = rootx;
node[rooty].op = (3 - node[y].op + d-1 + node[x].op) % 3;
}
int main()
{
scanf("%d%d",&n,&k);
Init();
while(k--){
scanf("%d%d%d",&a,&b,&c);
if(b > n || c > n){
ans++;
continue;
}
if(a == 2 && b == c){
ans++;
continue;
}
int x = Find(b);
int y = Find(c);
if(x != y) Union(b,c,x,y,a);
else{
if(a == 1 ) {
if(node[b].op != node[c].op)ans++;
}
else if(a == 2){
if( (node[c].op + 3 - node[b].op) % 3 != 1 ) ans++;
}
}
}
cout << ans << endl;
}
/*
100 10
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
2 3 4
2 4 3
1 2 3
*/
方法二:种类并查集
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstring>
#include<string>
#include<set>
#include<cmath>
#include<map>
#include<list>
#include<sstream>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long LL;
const int maxn = 50000 + 8;
int n,k,d,x,y,ans;
int rrank[maxn],par[maxn];
void Init()
{
memset(rrank,0,sizeof rrank);
memset(par,0,sizeof par);
ans = 0;
for(int i = 1; i <= n; ++i){
rrank[i] = 0;
par[i] = i;
}
}
int Find(int x)
{
if(x == par[x]) return x;
int fa = par[x];
par[x] = Find(par[x]);
rrank[x] = (rrank[x] + rrank[fa]) % 3;
return par[x];
}
bool Union(int x,int y,int rx,int ry,int d)
{
if(ry == rx) {
if (d == 1 && rrank[x] != rrank[y]) return false;
if (d == 2) {
/*if (rrank[y] == 1 && rrank[x] != 0) return false;
if (rrank[y] == 2 && rrank[x] != 1) return false;
if (rrank[y] == 0 && rrank[x] != 2) return false;*/
if( (rrank[y] + 3 - rrank[x]) % 3 != 1) return false;
}
return true;
}
if(ry != rx) {
par[ry] = rx;
rrank[ry] = (3 - rrank[y] + d - 1 + rrank[x]) % 3;
}
return true;
}
int main()
{
scanf("%d%d",&n,&k);
Init();
while(k--)
{
scanf("%d%d%d",&d,&x,&y);
if(x > n || y > n || (d == 2 && x == y) ){
ans++;
continue;
}
int a = Find(x);
int b = Find(y);
if(!Union(x,y,a,b,d)) {
ans++;
continue;
}
}
cout << ans << endl;
}