今天ac了不少题,大概能说关于“树”和“并查集”是学明白了一些了吧。
今天做了洛谷上的五道《并查集与树》类型的题,分别是
目录
2.[USACO3.4]美国血统 American Heritage - 洛谷
题目:
[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;
}