第七周小结

第七周小结


  这周主要看了并查集的例题, 并查集的算法并不复杂,主要是套模板。使用并查集大体分为三个步骤,建立新的集合( make( ) )、合并集合(union( ) )、查找根节点( find( ) )。并查集的题目无非就是这三个步骤,另外再根据题目的具体要求增加些操作,如输出集合个数,查找某个元素所在集合的元素个数等。只要思路明确,代码部分就不难写了,难点在于如何根据题目想到用并查集解题,我感觉解决这个问题可以靠积累题目来解决,见多识广就能想的全面,所以本周重点在于看题。下面是本周看的题目总结。

一、
1.疑似病人

题意:根据给出数据建立集合,查找元素0所在集合的元素个数。

题解:1.并查集三步走,新建,查找,合并,可以用路径压缩减少找到根节点的需要走的路径数。
2.可以用数组rank[ ]来存放某个集合的结点个数,初始值为1,每合并一个元素数组值加1。

题目没什么难度,注意数据的输入正确就好。

2.小希的迷宫

题意:并查集,合并到最后只有一个集合,且集合中没有环。

题解:1.根据输入的信息合并,标记每次合并访问的结点。
2.每次合并的同时检查被合并的结点是否标记过,若标记过,说明他俩已经在一个集合中,指向同一个根节点,此时进行合并就会形成一个环(结点1->根节点->结点2->结点1)。
3.找出集合的个数,为1则yes。

  • 如何判断集合的个数
    合并两个结点时,取其中一个结点作为根节点,并使另一个结点的值等于根节点的值,得到新的集合。一个集合中只有根节点的值等于他初始的值(一般来说新建集合时是s[ i ]=i,则只有根结点的值等于他本身),于是代码如下:

for(i=0;i<n;i++)
if(father[i]=i) t++;

  • 如何判断集合中有环

1.每次标记合并的结点,若合并到已被标记的两个结点,则集合中出现环。

2.经查找发现被合并的两个结点有相同的根节点,说明已在一个集合中,此时再合并就会形成环。

3.若边数大于点数-1,则形成环。

由于边数==点数-1,即构成环时边数大于点数-1,不连通时边数小于点数-1,由于两点构成一条边,对于输入的每两点,边总的计数加一,最后与输入不同点的个数减一所得的值比较,即可判断。

这个方法是看题解时学到的,不得不说人家的思路很巧妙,简洁明了,利用了C++STL中的set容器来实现,代码也很简单。果然知识都是融会贯通的,好的思维加上对代码实现的熟练简直就是一大利器。有时候题目不限于一种解题方法,要多多思考脑子灵活,这样思维才会慢慢提升。
这里整理一下别人的代码:

#include<stdio.h>
#include<set>
using namespace std;
set<int>S;
int main()
{
int a,b;
while(scanf("%d%d",&a,&b)&&(a!=-1||b!=-1))
{
if(a==0&&b==0) printf("Yes\n"); 
int num=1; 
S.insert(a);
S.insert(b);
while(scanf("%d%d",&a,&b)&&(a||b))
{
S.insert(a);
S.insert(b); 
num++; 
} 
if(S.size()-1==num) printf("Yes\n");
else printf("No\n"); 
S.clear(); 
} 
return 0; 
} 

3.通畅工程

题意:求最少再修几条路使每个村连通(即所有村在一个集合中)。

题解:1.并查集三步走,新建,查找,合并。
2.找出集合个数,最小修路数即为集合个数减1.

这道题目中规中矩没什么难度,就不加赘述了。

#include <iostream>
#include <algorithm>
#include <set>
#include <cstring>
using namespace std;
int pre[1010];
int find(int x)
{
	int res=x;
	while(pre[res]!=res) res=pre[res];
	return res;
}
 
void join(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy) pre[fy]=fx;
}
 
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n,m;
	while(cin>>n>>m&&n)
	{
		int a,b;
		memset(pre,0,sizeof(pre));
		for(int i=1;i<=n;i++) pre[i]=i;
		for(int i=0;i<m;i++)
		{
			cin>>a>>b;
			join(a,b);
		}
		set<int> st;
		st.clear();
		for(int i=1;i<=n;i++)
		{
			st.insert(find(i));
		}
		cout<<st.size()-1<<endl;
	}
	return 0;
}

二、
总结1:
  看过这些题目对并查集也有了大概的了解,这些题目的共同点是,将某些元素归为一队,放在一起,也就是放在一个集合中。有点像上学时的分班,这些同学去一班,那些同学去二班,于是一堆学生就分成了两个班(两个集合);每个班需要一个代表全体同学说话的人,也就是班长,班长的地位就可以类比为集合中的根节点。遇到有类似性质的题目,就可以考虑使用并查集求解。

  路的连通性问题可以与并查集相联系。以村庄修路为例,每修一条路,路两头的村子连接起来,即可以看做两个元素联系在一起(形成一个集合),求所有村子直接或间接相连,即让所有村子在一个集合中。所以类似这样的连通问题可以优先考虑使用并查集求解。

总结2:
一些新学到的:
1)输入外挂
既然叫外挂,那他一定在某方面有优势,那就是提高效率,一般用在大量输入输出的情况下。

inline int read()
{ char c=getchar();
  int x=0,f=1;
  while(c<'0'||c>'9') 
  {if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9')
  {x=x*10+c-'0';c=getchar();}
  return x*f;
}

2)没有输入时结束循环

  • scanf(“%d%d”,&n,&m)!=EOF
  • while(~scanf(“%d”,&n))

总结3:
  慢慢熟悉这种学习的节奏了,感觉自己有些地方还需要改进,比如看题速度等。千万不能只看不做,代码要勤写,同时一定要注意随时总结,你总结出来的才是你真正掌握的知识。下周再接再厉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值