这个博客感觉对于并查集入门写的很好——并查集入门
A - How Many Tables
题意:n个人给m个关系,每个关系给出两个数字 A,B,代表A认识B,规则: A认识B,B认识C,则ABC可以在一桌吃饭,问给出m个关系后,至少需要多少个桌子可以容纳下这n个人。
思路:这很明显就是并查集这种结构,处理不相交集合的合并和查询问题,问题就是求一个深林中有几棵树的问题。一般来说,一个并查集对应三个操作:
1、初始化( Init()函数 )
2、查找函数( Find()函数 )
3、合并集合函数( merge()函数 )
#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long ll;
using namespace std;
ll c[1010];
int find( int num ){
if( c[num] != num ){//剪枝,如果发现当前结点的祖宗结点不是自己了,说明进行了merge操作
c[num] = find( c[num] );//保持更新
}
return c[num];
}
void merge( int a, int b ){
c[find(a)] = find(b);//注意merge是有方向性的
}//把b结点的祖宗结点和a结点的祖宗结点连接,这样ab就完全真的连接上了
int main()
{
ios :: sync_with_stdio( false );
cin.tie( NULL );
ll t, a, b, n, m;
cin >> t;
while( t-- ){
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
c[i] = i;
}//初始化,一般并查集 我们常常从下标 1 开始
while( m-- ){
cin >> a >> b;
merge( a, b );//合并函数
}
ll cnt = 0;
for( int i = 1; i < n + 1; i++ ){
if( c[i] == i ){//当把连接建立完成后,就找看有多少个祖宗结点,就有多少棵树,
//要理解这段代码的含义
++cnt;
}
}
cout << cnt << endl;
}
return 0;
}
B - How Many Answers Are Wrong
题意:输入一段长度为n的序列,有q个信息,每个信息给出三个数据,从L——R(含)(数组下标从1开始)的和,问q个信息中有多少是假的,信息依次给出,判断依据是由上面信息来判断下面信息,即是说第一条信息一点是对的,因为没有其他依据来反驳它,这一点很重要。
思路:这道题我们以左端点为祖宗结点,如果两个集合的左端点相同那么就可以根据前缀和来检查是否符合条件,同时计数,如果两个集合的祖宗节点不一样,那就合并。合并过程其他很普通除了,计算sum,这里可以看看此题别人的题解,还要注意方向——哪个是祖宗节点,还要注意的一点为了方便合并,我们建立 ( ]区间。
#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long ll;
const int N = 2e5 + 10;
using namespace std;
int n, m, p[N], sum[N], cnt;
void ini( ){
for( int i = 0; i < n + 1; i++ ){
p[i] = i;
}
memset( sum, 0, sizeof( sum ) );
}
int find( int x ){
if(p[x] == x) return x;//减小复杂度
int u = find(p[x]);
sum[x] += sum[p[x]];
return p[x] = u;
}
void merge( int a, int b, int s ){
int fa = find(a - 1) ,fb = find(b);
p[fb] = fa;
sum[fb] = s + sum[a - 1] - sum[b]; //一定要理解怎么算的
}
int main()
{
int a, b, s;
while( cin >> n >> m ){
ini( );
cnt = 0;
while( m-- ){
cin >> a >> b >> s;
//a -= 1;
int aan = find( a - 1);
int ban = find( b );
if( aan == ban ){
if( sum[b] - sum[a - 1] != s ){//类似于前缀和
++cnt;
}
}
else{
merge( a, b, s );
}
}
cout << cnt << endl;
}
return 0;
}
总结:
学习了并查集感觉比较抽象,各种找祖宗节点,特别是带权并查集这些,感觉自己学的不太好。但是知道了这种数据结构的用途,就是判断一个森林中有几棵树、某个节点是否属于某棵树等,但要灵活使用还有一定的难度.