2022.1.20 学习总结

今天ac了不少题,大概能说关于“树”和“并查集”是学明白了一些了吧。

 今天做了洛谷上的五道《并查集与树》类型的题,分别是

目录

1.[NOIP2001 普及组] 求先序排列 - 洛谷​

2.[USACO3.4]美国血统 American Heritage - 洛谷

3.搭配购买 - 洛谷

4.朋友 - 洛谷

5.修复公路 - 洛谷

题目:

[NOIP2001 普及组] 求先序排列 - 洛谷

 题目描述很精简,短短几行而已。要求很明确,根据输入的两串分别按中序排列、后序排列的字符串明确树的结构,再通过“前序排列”将其输出

 根据中序排列规则是“左->根->右”,后序排列规则是“左->右->根”,可以推知,后序排列的末尾元素就是整棵树的根节点,从后往前推,每个元素都是上个节点的右子树的父节点。

以题目给出的测试样例为例,那么可以找出根节点是A,这时再考虑中序排列的规律,可以推知,根节点的左侧元素为它的左子树节点,而右侧元素则为右子树节点。

想到这里,那么就会很自然地想到,其它子节点都可以这么推导然后得到它的左右子节点,那么题目就转换成了一个dfs类型的题目。

这里将中序排列与后序排列的范围区间作为参数,注意递归时传递的参数需要按“当前考虑的树”来判断,所以递归时要找出原树与递归子树的区间关系,根据这个来传递参数。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char s1[10],s2[10];
void build(int lhead,int ltail,int rhead,int rtail)//当前考虑的树
//中序遍历范围为[lhead,ltail],后序遍历范围为【rhead,rtail】 
{
	int i,j,k,p=lhead;
	if( lhead < 0|| rhead < 0 ) //遍历发现不合法数据,返回 
	return;
    if( lhead > ltail || rhead > rtail ) //遍历发现为空树时返回 
	return;                 
    printf("%c",s2[rtail]);        //后序遍历末位元素为当前考虑的树的根节点,直接输出                             
    while(s1[p] != s2[rtail])           //在中序遍历字符串中查找 
	p++;                             //从中序遍历中找出左树的范围
	k=p-lhead;                      //计算左子树节点个数
    build(lhead,p-1,rhead,rhead+k-1);//递归判断当前节点的左子树
	//其中序范围为[lhead,p-1]后序范围为[rhead,rhead+k-1] 
    build(p+1,ltail,rhead+k,rtail-1);       //递归当前节点的右子树,同上 
}
int main()
{
    scanf("%s",s1);
    scanf("%s",s2);
    int length;
    length=strlen(s1)-1;
    build(0,length,0,length);                            
}

 题目:

[USACO3.4]美国血统 American Heritage - 洛谷

 这个题和上一题是差不多的,搞明白了上一题,这一题也没有很大的难度,(所以我的注释也少了很多)只是注意一下,这次的输入是“中序排列”字符串和“前序排列”字符串,所以找根节点或父节点时是从首位开始找。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char s1[100],s2[100];
void dfs(int lhead,int ltail,int rhead,int rtail)//四个参数分别表示当前遍历的树的 "中序遍历范围" 以及 "前序遍历范围" 
{
	int i=0,j=0,k=0;
	if(lhead < 0 || lhead > ltail || rhead < 0 || rhead > rtail)
	return;
	char z=s2[rhead];
	while(s1[j] != z){
		j++;
	}
//	printf("%d",j);
    dfs(lhead,j-1,rhead+1,rhead-lhead+j);//递归遍历当前考虑节点的左子树
	dfs(j+1,ltail,rhead-lhead+j+1,rtail);//递归遍历当前考虑节点的右子树
	printf("%c",s1[j]); 
}
int main()
{
	scanf("%s",s1);//中序遍历 
	scanf("%s",s2);//前序遍历 
	int i,j,ls1;
	ls1=strlen(s1)-1;
	dfs(0,ls1,0,ls1);
	return 0;
}

题目:

搭配购买 - 洛谷

这题是我昨天留下的题,今天重新思考了一遍后很快就ac了。

按题意来说,就是要将有关系的云朵打包成一个集合,最后在当前有限的钱数下尽可能买到最高价值的云朵集合。

简单说来就是“并查集”+“01背包”。

在这题里,可以将集合个数看作“可以放进背包的物品个数”,将集合价值看作“物品价值”,弄明白这些后就可以开始敲代码了

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int n,m,w,parent[100010],dp[100010];
int max (int x,int y)
{
	return (x>y?x:y);
}
struct abc
{
	int value;
	int price;
}tree[100010]; 
int find(int x)//查找根节点 
{
	if(parent[x] != x )
	parent[x]=find(parent[x]);
	return parent[x];
}
void union_parent(int x,int y)//将两节点建立联系,即“合并 ” 
{
	if( find(x) != find(y) )
	parent[find(x)]=find(y);//该语句表示find(x)的父节点为find(y)
}
int main()
{
	scanf("%d %d %d",&n,&m,&w);
	int i,j,a,b,v,ans=-9999;
	for(i=1;i<=n;i++){
	parent[i]=i;
	scanf("%d %d",&tree[i].price,&tree[i].value);}
	for(i=1;i<=m;i++){
	scanf("%d %d",&a,&b);
	union_parent(a,b);//建立关系 
	}
    for(i=1;i<=n;i++){
		if(parent[i] != i) {/*不是根的话,把同一个集合的price和value累加到根的那个数组 */
			tree[find(i)].price += tree[i].price;  //累加到根的那个数组
	    	tree[find(i)].value += tree[i].value;
		    tree[i].price=0;    //加过就更新为0,避免之后遍历到时再次加入 
		    tree[i].value=0;
		}
	}
	for(i=1;i<=n;i++)   //01背包问题:先循环遍历所有物品(所有云朵集合) 
		for(v=w;v>=tree[i].price;v--)
           dp[v] = max(dp[v],dp[v-tree[i].price]+tree[i].value);
    
    printf("%d",dp[w]);
	return 0; 
}

题目:
朋友 - 洛谷

已知有A,B两家公司,分别有员工n,m,人,在A公司的n个人中存在p对好友关系,在B公司的m个人中存在q对好友关系,同时小明小红编号分别为1,-1,且两者为情侣关系。

A公司都是男性,编号为正,B公司都是女性,编号为负。

要求输出包括小明小红在内,小明小红最多能促成几对情侣出现。

 (实际上我感觉题目表述有些问题来着)

简单说来就是求“与小明认识的人”和“与小红认识的人”两个数值之间的较小数(明明两句话就可以说完的题硬是说成我一时半会没看明白的样子)

很好解决,开一个足够大的数组,要能同时装下两家公司的所有员工,然后分别“ 合并 ”,计算“与小明认识的人”和“与小红认识的人”两个数值后输出较小值就好了。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int n,m,p,q,parent[100010],ans=0,ans1=0,ans2=0;
int min(int x,int y)
{
	return (x<y?x:y);
}
void union_parent(int x,int y)
{  
	if(find(x) != find(y))
	parent[find(x)]=find(y);
}
int find(int x)
{
	if(parent[x] != x)
	parent[x]=find(parent[x]);
	return parent[x];
}
int main()
{
	scanf("%d %d %d %d",&n,&m,&p,&q);
	int i,j,a,b;
	for(i=0;i<=n+m;i++)
	parent[i]=i;
	for(i=1;i<=p;i++){
		scanf("%d %d",&a,&b);
		union_parent(a,b);
	}
	for(i=1;i<=q;i++){
		scanf("%d %d",&a,&b);
		a*=-1;
		b*=-1;
		union_parent(a+n,b+n);
	}
	for(i=1;i<=n;i++)
		if(find(i) == find(1))
		ans1++;
    for(i=n+1;i<=n+m;i++)
		if(find(i) == find(n+1))
		ans2++;
	ans=min(ans1,ans2);
	printf("%d",ans);
	return 0;
}

题目:
修复公路 - 洛谷

已知有n个村庄,m条将要打通的道路,知道每条道路需要打通将花费的时间。输出所有村庄连通时的最短时间。

既然要求最短时间,那么我设立了一个快排函数,用于按时间长短来给每条道路排序。

我设了一个结构体变量,用来存储每组道路数据,然后经过排序后在拿出来一 一连通,同时循环判断是否所有村庄都连通了,(就是一个简单的嵌套循环)若是,则直接输出当前遍历到的道路所需时间并终止程序运行,反之,若循环完全结束后程序仍在执行则输出“-1”,表示“所有道路打通后也无法做到连通所有村庄”

这里注意一下,直接嵌套容易时间超限,所以我们对于内部的嵌套的循环只要取到n/2就行了,也足够判断所有村庄了。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int parent[100010],n,m;
struct abc
{
	int x,y,t;
}a[100010],t1;
int find(int x)
{
	if(parent[x] != x)
	parent[x]=find(parent[x]);
	return parent[x];
} 
void union_parent(int x,int y)
{
	if(find(x) != find(y))
	parent[find(x)]=find(y);
}
void quicksort(int left,int right)
{
	int i,j,temp;
	if(left > right)
	return ;
	i=left;
	j=right;
	temp=a[(left+right)/2].t;
	while(i <= j){
		while( a[j].t > temp )
		j--;
		while( a[i].t < temp )
		i++;
		if( i <= j ){
			t1=a[i];
			a[i]=a[j];
			a[j]=t1;
			i++;
			j--;
		}
	}
	if(left < j) quicksort(left,j);
    if(i < right) quicksort(i,right);
	return;
}
int main()
{
	scanf("%d %d",&n,&m);
	int i,j,k,flag=0;
	for(i=0;i<=n;i++)
	parent[i]=i;
	for(i=1;i<=m;i++)
		scanf("%d %d %d",&a[i].x,&a[i].y,&a[i].t);
	quicksort(1,m);
	for(i=1;i<=m;i++)
	{
		flag=0;
		union_parent(a[i].x,a[i].y);
		for(j=1;j<n/2;j++){
		if(find(j) != find(n-j+1)){
		flag=-1;
		break;}
		}
		if( flag == 0 ){
			printf("%d",a[i].t);
			exit(0);}
	}
	printf("-1");
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值