精讲并查集经典习题:P1892 [BOI2003]团伙(超详细)


 一,需要开o2才能AC的代码

用emys[i]存储第i个人的敌人数,vector<long long>emyr[i]存储i个人的所以敌人,fa[i]存储第i个人的大boss。

1.初始化   把fa[i]全部设为i

2.在输入时如果u跟v是朋友,则join(u,v);否则说明是敌人,那么u,v的敌人数++,再各自把对方加入自己的敌人中

3.遍历所有人,如果某一人的敌人数>1,则让他的所有敌人两两组队

4.最后数一下有多少个大boss就能知道有多少个团体了。

代码:


#include <bits/stdc++.h>
using namespace std;
long long n,m,fa[1000001],emys[1000001],ans,u,v,a,b;
vector<long long>emyr[10001];
char t;
long long find(int x)
{
  if(fa[x] == x) return x;
  else return find(fa[x]);
}
void join(int a,int b)
{
  int x = find(a),y = find(b);
  if(x != y) fa[x] = y;
}
int main()
{
  scanf("%lld%lld",&n,&m);
  for(int i = 1; i <= n; i++) fa[i] = i;
  for(int i = 1; i <= m; i++)
  {
    scanf("%s%lld%lld",&t,&u,&v);
    if(t == 'F') join(u,v);
    else
    {
      emys[u]++;
      emys[v]++;
      emyr[u].push_back(v);
      emyr[v].push_back(u);
    }
  }
//  for(int i = 1; i <= n; i++) cout<<emys[i]<<" ";
//  cout<<endl;
//  for(int i = 1; i <= n; i++)
//  {
//    for(int j = 0; j < emyr[i].size(); j++)
//      cout<<emyr[i][j]<<" ";
//    cout<<endl;
//  }
  for(int i = 1; i <= n; i++)
  {
    if(emys[i] > 1)
    {
      for(int j = 0; j < emys[i]; j++)
        for(int k = j + 1; k < emys[i]; k++)
          join(emyr[i][j],emyr[i][k]);
    }
  }
  for(int i = 1; i <= n; i++)
    if(fa[i] == i)
      ans++;
  printf("%lld",ans);
  return 0;
}

没开o2优化: 

开了o2优化:


二,不用开o2优化也能AC的代码(1)

首先,要充分理解题目。 “敌人的敌人就是朋友”可以这么理解:如果一个人有两个或更多敌人,这些敌人就应该被合并。

代码中的emy数组就是记录了每个人的第一个敌人,再遇到敌人时就把这两个敌人合并。

其他的跟前面一样。

没想出这点的话还是蛮难做的。


#include <bits/stdc++.h>
using namespace std;
long long n,m,fa[1000001],emys[1000001],ans,u,v,a,b;
vector<long long>emyr[10001];
char t;
long long find(int x) //寻找x的大boss
{
  if(fa[x] == x) return x;
  else return find(fa[x]);
}
void join(int a,int b)//合并
{
  int x = find(a),y = find(b);
  if(x != y) fa[x] = y;
}
int main()
{
  scanf("%lld%lld",&n,&m);
  for(int i = 1; i <= n; i++) fa[i] = i;
  for(int i = 1; i <= m; i++)
  {
    scanf("%s%lld%lld",&t,&u,&v);
    if(t == 'F') join(u,v);//是朋友就合并 
    else
    {
      if(emys[u] == 0) emys[u] = find(v);//一个人有两个或更多敌人,合并他们 
      else join(v,emys[u]);
      if(emys[v] == 0) emys[v] = find(u);
      else join(u,emys[v]);
    }
  }
//  for(int i = 1; i <= n; i++) cout<<emys[i]<<" ";
//  cout<<endl;
//  for(int i = 1; i <= n; i++)
//  {
//    for(int j = 0; j < emyr[i].size(); j++)
//      cout<<emyr[i][j]<<" ";
//    cout<<endl;
//  }
//  for(int i = 1; i <= n; i++)
//  {
//    if(emys[i] > 1)
//    {
//      for(int j = 0; j < emys[i]; j++)
//        for(int k = j + 1; k < emys[i]; k++)
//          join(emyr[i][j],emyr[i][k]);
//    }
//  }
  for(int i = 1; i <= n; i++)
    if(fa[i] == i)
      ans++;
  printf("%lld",ans);
  return 0;
}

 三,引入反集

a 表示 a 的朋友集,a + n 表示 a 的敌人集

1.如果 a 和 b 是朋友,那么 将 a 和 b 的朋友集 相连即可: p[find(a)] = find(b);
2/如果 a 和 b 是敌人,则 将 a 的敌人集与 b 的朋友集 相连,将 b 的敌人集与 a 的朋友集 相连,即: p[find(a + n)] = Find(b),p[find(b + n)]  =  find(a)
最后统计有多少团伙时,1 ~ n 中(朋友集)所有 p[i] == i 的人数就是答案。因为根据题意,所有团伙一定会分布在朋友集中,因此 只要在朋友集中统计没有祖先的节点个数即可。

代码(以下代码转载自洛谷 P1892 [BOI2003]团伙(并查集变种 反集)_Brightess的博客-CSDN博客):

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>

using namespace std;
//#define int long long
int n, m;
const int N = 1010;
int p[N << 1];

int Find(int x)
{
	if (p[x] != x) p[x] = Find(p[x]);
	return p[x];
}

signed main()
{
	int T = 1; //cin >> T;

	while (T--)
	{
		cin >> n >> m;
		int res = 0;
		for (int i = 1; i < N<<1; ++i) p[i] = i;
		for (int i = 1; i <= m; ++i)
		{
			char op[2]; int a, b;
			cin >> op >> a >> b;
			int pa = Find(a), pb = Find(b);
			int pan = Find(a + n), pbn = Find(b + n);
			if (*op == 'F')
			{
				p[pb] = pa;
			}
			else
			{
				p[pbn] = pa;
				p[pan] = pb;
			}
		}
		for (int i = 1; i <= n; ++i) if (p[i] == i) ++res;
		cout << res << '\n';
	}

	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值