4.20天梯模拟赛(并查集,搜索树)

7-7 社交集群 (30 分)

当你在社交网络平台注册时,一般总是被要求填写你的个人兴趣爱好,以便找到具有相同兴趣爱好的潜在的朋友。一个“社交集群”是指部分兴趣爱好相同的人的集合。你需要找出所有的社交集群。

输入格式:
在这里插入图片描述

输出格式:
首先在一行中输出不同的社交集群的个数。随后第二行按非增序输出每个集群中的人数。数字间以一个空格分隔,行末不得有多余空格。

输入样例:

8
3: 2 7 10
1: 4
2: 5 3
1: 4
1: 3
1: 4
4: 6 8 1 5
1: 4

输出样例:

3
4 3 1

这道题的爱好是一个可变的集合,我们通过枚举人的爱好更新这个集合并记录这个集合中的人数。通过并查集更新连通块的时候用一个a数组记录每个人的第一个爱好作为标记,相当于这个人最后拿着这个标记作为信号跟有缘人走到一起,形成一个社交集群。

#include
#include
#include
using namespace std;
int n,x,y,k=0;
int p[1010];
int a[1010];
int cnt[1010];
int vis[1010];
int ans=0,mx=-1;//连通块个数,枚举的最多数量(当然也可以枚举到1010) 
int find(int x)
{
     
	if(p[x]!=x) return p[x]=find(p[x]);
	else return x;
}
bool cmp(int a,int b)
{
     
	return a>b;
}
int main()
{
     
	cin>>n;
	for(int i=1;i<=1010;i++) p[i]=i;//开始没有记录最大值,只能初始化到边界 
	for(int i=1;i<=n;i++)//8个人 
	{
     
		string op;
		cin>>op>>y;
		a[i]=y;//记录每个人第一个爱好,以统计连通块人数 
		if(mx<y) mx=y;
		x=op[0]-'0';
		x-=1;
		while(x--)
		{
        int pp;
		    cin>>pp;
		    if(pp>mx) mx=pp;
			int a=find(y);
			int b=find(pp);
			if(a!=b)
			{
     
				p[a]=b;
			}
		}
	}
	for(int i=1;i<=n;i++)//8个人 
	{
     
		int t=find(a[i]);//指向每个连通块的终点 
		cnt[t]++;//根节点的人数 += 当前枚举的这个人数 
	}
	sort(cnt+1,cnt+mx+1,cmp);
	for(int i=1;i<=n;i++)//8个人 
	{
     
		if(cnt[i]!=0) ans++;//在这些爱好中,非根节点和未出现的爱好cnt=0.
		//这两种不管哪种情况都不计数,只计数根节点。 
	}
	printf("%d\n",ans);//连通块个数 
	for(int i=1;i<=ans;i++)
	{
     
		printf("%d",cnt[i]);
		if(i==ans) puts("");
		else printf(" ");
	}
} 

7-5 文件传输 (25 分)

这题得了24分,注释的地方写错了,确实写的短就要多注意细节。

#include
#include
using namespace std;
int n,m;
const int N=1e4+10;;
int p[N];
int cnt[N];
int find(int x)
{
     
	if(x!=p[x]) return p[x]=find(p[x]);
	else return x;
}
int main()
{
     
	cin>>n;
	for(int i=1;i<=n;i++) p[i]=i;
	while(1)
	{
        
		string op;
		int x,y;
		cin>>op;
		if(op[0]=='S') break;
		cin>>x>>y;
		if(op[0]=='C')
		{
     
			if(find(x)!=find(y))
			  puts("no");
			else puts("yes"); 
		}
		else if(op[0]=='I')
		{
       
			if(find(x)!=find(y))
			{
     
				p[find(x)]=find(y);//注意不是p[x]=y;
			}
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
     
		if(p[i]==i) cnt++;
	}
if(cnt==1) cout<<"The network is connected.\n";
	else cout<<"There are "<<cnt<<" components.\n";
}

7-3 朋友圈 (25 分)

某学校有N个学生,形成M个俱乐部。每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出,如果A和B是朋友,且B和C是朋友,则A和C也是朋友。请编写程序计算最大朋友圈中有多少人。

输入格式:
输入的第一行包含两个正整数N(≤30000)和M(≤1000),分别代表学校的学生总数和俱乐部的个数。后面的M行每行按以下格式给出1个俱乐部的信息,其中学生从1~N编号:

第i个俱乐部的人数Mi(空格)学生1(空格)学生2 … 学生Mi

输出格式:
输出给出一个整数,表示在最大朋友圈中有多少人。

输入样例:

7 4
3 1 2 3
2 1 4
3 5 6 7
1 6

输出样例:

4

这道题也是并查集,要注意这道题和第一题的cnt初始化是有区别的:
4.20天梯模拟赛(并查集,搜索树)_第1张图片
如果对于一个人 4 他没有兴趣爱好:
4.20天梯模拟赛(并查集,搜索树)_第2张图片
那么他的p[i]=i且,cnt[i]=1的数据我们无法判断他是有一个仅有4的连通块,还是没有这个连通块。
所以如果这道题像第一个题一样要求把每个连通块内部元素个数排序的话,需要再开一个vis数组记录出现过的人,只有一个人出现过,且p[i] = i,且 cnt[i] =1 我们才能认为他是一个孤立点组成的连通块。

#include 
#include
#include
using namespace std;
const int N=30010;
int n,m;
int p[N];
int cnt[N];
int vis[N];
int find(int x)
{
     
	if(x!=p[x]) return p[x]=find(p[x]);
	else return x;
}
bool cmp(int a,int b)
{
     
	return a>b;
}
int main()
{
     
	cin>>n>>m;
	for(int i=1;i<=n;i++) p[i]=i,cnt[i]=1;
	for(int i=1;i<=m;i++)
	{
     
		int x,par,pp,y;
		cin>>x>>pp;
		par=find(pp);
		for(int j=1;j<=x-1;j++)
		{
     
			cin>>y;
			int py=find(y);
			if(par!=py)
			{
     
				p[py]=par;//形成朋友圈 
				cnt[par]+=cnt[py];//统计每一个连通块个数 
			}
		}
	}
	int t=-1;
	for(int i=1;i<=n;i++)
	{
     
		t=max(t,cnt[i]);
	}
	printf("%d\n",t);
}

7-4 部落 (25 分)

在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不同的朋友圈。我们认为朋友的朋友都算在一个部落里,于是要请你统计一下,在一个给定社区中,到底有多少个互不相交的部落?并且检查任意两个人是否属于同一个部落。

输入格式:
输入在第一行给出一个正整数N(≤10^4​​ ),是已知小圈子的个数。随后N行,每行按下列格式给出一个小圈子里的人:
K P[1] P[2] ⋯ P[K]
其中K是小圈子里的人数,P[i](i=1,⋯,K)是小圈子里每个人的编号。这里所有人的编号从1开始连续编号,最大编号不会超过10^4。

之后一行给出一个非负整数Q(≤10^4 ),是查询次数。随后Q行,每行给出一对被查询的人的编号。

输出格式:
首先在一行中输出这个社区的总人数、以及互不相交的部落的个数。随后对每一次查询,如果他们属于同一个部落,则在一行中输出Y,否则输出N。

输入样例:

4
3 10 1 2
2 3 4
4 1 5 7 8
3 9 6 4
2
10 5
3 7

输出样例:

10 2
Y
N

简单并查集的应用
这道题我改了一个世纪都不知道自己哪错了,最后发现一个事情:
如果最开始初始化p的范围等于const int N 的这个数,这个题就给我1分,只要让这俩数不相等马上就AC了,不知道这道题是不是编译器的问题,真是吐了。
4.20天梯模拟赛(并查集,搜索树)_第3张图片
4.20天梯模拟赛(并查集,搜索树)_第4张图片
4.20天梯模拟赛(并查集,搜索树)_第5张图片
真的无语!

#include 
#include
#include
using namespace std;
const int N=1e4+10;
int n,m;
int p[N];
int vis[N];
int mx;
int find(int x)
{
     
	if(x!=p[x]) return p[x]=find(p[x]);
	else return x;
}
int main()
{
     
	cin>>m;
	for(int i=1;i<=10009;i++) p[i]=i;
	mx=0;
	for(int i=1;i<=m;i++)
	{
     
		int x,par,pp,y;
		cin>>x>>pp;
		par=find(pp);
		if(pp>mx) mx=pp;
		for(int j=1;j<=x-1;j++)
		{
     
			cin>>y;
			int py=find(y);
			if(par!=py)
			{
     
				p[py]=par;//形成朋友圈 
				if(y>mx) mx=y;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=mx;i++)
	{
     
		if(p[i]==i) ans++;
	}
	printf("%d %d\n",mx,ans);
	int q;
	cin>>q;
	while(q--)
	{
     
		int a,b;
		cin>>a>>b;
		if(find(a)!=find(b)) puts("N");
		else puts("Y");
	}
	return 0;
}

7-8 是否同一棵二叉搜索树 (25 分)

给定一个插入序列就可以唯一确定一棵二叉搜索树。然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到。例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结果。于是对于输入的各种插入序列,你需要判断它们是否能生成一样的二叉搜索树。

输入格式:
输入包含若干组测试数据。每组数据的第1行给出两个正整数N (≤10)和L,分别是每个序列插入元素的个数和需要检查的序列个数。第2行给出N个以空格分隔的正整数,作为初始插入序列。最后L行,每行给出N个插入的元素,属于L个需要检查的序列。

简单起见,我们保证每个插入序列都是1到N的一个排列。当读到N为0时,标志输入结束,这组数据不要处理。

输出格式:
对每一组需要检查的序列,如果其生成的二叉搜索树跟对应的初始序列生成的一样,输出“Yes”,否则输出“No”。

输入样例:

4 2
3 1 4 2
3 4 1 2
3 2 4 1
2 1
2 1
1 2
0

输出样例:

Yes
No
No

思路:分别建二叉排序树,然后进行递归比较。

#include
using namespace std;
int n,m;
struct node
{
     
	int data;
	struct node *lc,*rc;
};
void build(node *root,int x)
{
     
	if(x<root->data)
	{
     
		if(root->lc)
		{
     
			build(root->lc,x);
		}
		else 
		{
     
			node *p=new node;
			p->data=x;
			p->lc=NULL;
			p->rc=NULL;
			root->lc=p;
			return ;
		}
	}
	else
	{
     
		if(root->rc)
		{
     
			build(root->rc,x);
		}
		else 
		{
     
			node *p=new node;
			p->data=x;
			p->lc=NULL;
			p->rc=NULL;
			root->rc=p;
			return ;
		}		
	}
}
bool judge(struct node *root,struct node *rot)
{
     
    if(root==NULL&&rot==NULL) return true;
    if(root==NULL||rot==NULL) return false;
    if(root->data!=rot->data) return false;
    return judge(root->lc,rot->lc)&&judge(root->rc,rot->rc);
}
int main()
{
     
	while(cin>>n)
	{
        if(n==0) return 0;
	    cin>>m;
		node *root=new node;
		for(int i=1;i<=n;i++)
		{
     
			int x;
			scanf("%d",&x);
			if(i==1)
			{
     
			  root->data=x;
			  root->lc=NULL;
			  root->rc=NULL;
			}
			else
			{
     
				build(root,x);
			}
		}
		while(m--)
		{
     
		node *rot=new node;
		for(int i=1;i<=n;i++)
		{
     
			int y;
			scanf("%d",&y);
			if(i==1)
			{
     
			  rot->data=y;
			  rot->lc=NULL;
			  rot->rc=NULL;
			}
			else
			{
     
				build(rot,y);
			}
		}
		if(judge(root,rot)) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
		free(rot); 
		}
	}
	
	
}

排序二叉树的板子也可以短一些:

node *build (node *root,int x)
{
     
    if(root==NULL)
    {
     
        root=new node;
        root->data=x;
        root->lc=root->rc=NULL;
        return root;
    }
    else
    {
     
        if(x<root->data)
            root->lc=build(root->lc,x);
        else
            root->rc=build(root->rc,x);
    }
    return root;
}

7-10 搜索树判断 (25 分)

对于二叉搜索树,我们规定任一结点的左子树仅包含严格小于该结点的键值,而其右子树包含大于或等于该结点的键值。如果我们交换每个节点的左子树和右子树,得到的树叫做镜像二叉搜索树。

现在我们给出一个整数键值序列,请编写程序判断该序列是否为某棵二叉搜索树或某镜像二叉搜索树的前序遍历序列,如果是,则输出对应二叉树的后序遍历序列。

输入格式:
输入的第一行包含一个正整数N(≤1000),第二行包含N个整数,为给出的整数键值序列,数字间以空格分隔。

输出格式:
输出的第一行首先给出判断结果,如果输入的序列是某棵二叉搜索树或某镜像二叉搜索树的前序遍历序列,则输出YES,否侧输出NO。如果判断结果是YES,下一行输出对应二叉树的后序遍历序列。数字间以空格分隔,但行尾不能有多余的空格。
输入样例1:

7
8 6 5 7 10 8 11

输出样例1:

YES
5 7 6 8 11 10 8

输入样例2:

7
8 6 8 5 10 9 11

输出样例2:

NO

原文链接
作者代码写的挺清晰的,先求出二叉排序树,然后求出二叉搜索树的前序遍历,并将其存入数组,对于镜像二叉搜索树的前序,只需要把二叉搜索树的根左右变为根右左来遍历,那么就相当于遍历了镜像二叉搜索树,最后把这两个数组与题目数据对比,处理一下后序输出即可。

#include
using namespace std;
struct node
{
     
    node *l,*r;
    int data;
};
int a[10001],b[10001],c[10001];
int top,tot,flag1=1,flag2=1,flag3,flag4;
node *create(node *root,int x)
{
     
    if(root==NULL)
    {
     
        root=new node;
        root->data=x;
        root->l=root->r=NULL;
        return root;
    }
    else
    {
     
        if(x<root->data)
            root->l=create(root->l,x);
        else
            root->r=create(root->r,x);
    }
    return root;
}
void show1(node *root)
{
     
    if(root)
    {
     
        b[++top]=root->data;
        show1(root->l);
        show1(root->r);
    }
}
void show2(node *root)
{
     
    if(root)
    {
     
        c[++tot]=root->data;
        show2(root->r);
        show2(root->l);
    }
}
void finallshow1(node *root)
{
     
    if(root)
    {
     
        finallshow1(root->l);
        finallshow1(root->r);
        if(flag3==0)
        {
     
            cout<<root->data;
            flag3=1;
        }
        else
            cout<<" "<<root->data;
    }
}
void finallshow2(node *root)
{
     
    if(root)
    {
     
        finallshow2(root->r);
        finallshow2(root->l);
        if(flag4==0)
        {
     
            cout<<root->data;
            flag4=1;
        }
        else
            cout<<" "<<root->data;
    }
}
int main()
{
     
    int n;
    cin>>n;
    node *root=NULL;
    for(int i=1;i<=n;i++)
    {
     
        cin>>a[i];
        root=create(root,a[i]);//先建立二叉搜索树
    }
    show1(root);//分别将先序遍历和镜像先序遍历放入b和c数组里
    show2(root);
    for(int i=1;i<=n;i++)//只要有1个不同,就说明不是该树的遍历
    {
     
        if(a[i]!=b[i])
        {
     
            flag1=0;
            break;
        }
    }
    for(int i=1;i<=n;i++)
    {
     
        if(a[i]!=c[i])
        {
     
            flag2=0;
            break;
        }
    }
    if(flag1)
    {
     
        cout<<"YES"<<endl;
        finallshow1(root);//树的遍历
    }
    else if(flag2)
    {
     
        cout<<"YES"<<endl;
        finallshow2(root);//树的镜像遍历
    }
    else
        cout<<"NO"<<endl;
    return 0;
}

7-9 树种统计 (25 分)

随着卫星成像技术的应用,自然资源研究机构可以识别每一棵树的种类。请编写程序帮助研究人员统计每种树的数量,计算每种树占总数的百分比。

输入格式:
输入首先给出正整数N(≤10^​5),随后N行,每行给出卫星观测到的一棵树的种类名称。种类名称由不超过30个英文字母和空格组成(大小写不区分)。

输出格式:
按字典序递增输出各种树的种类名称及其所占总数的百分比,其间以空格分隔,保留小数点后4位。

输入样例:

29
Red Alder
Ash
Aspen
Basswood
Ash
Beech
Yellow Birch
Ash
Cherry
Cottonwood
Ash
Cypress
Red Elm
Gum
Hackberry
White Oak
Hickory
Pecan
Hard Maple
White Oak
Soft Maple
Red Oak
Red Oak
White Oak
Poplan
Sassafras
Sycamore
Black Walnut
Willow

输出样例:

Ash 13.7931%
Aspen 3.4483%
Basswood 3.4483%
Beech 3.4483%
Black Walnut 3.4483%
Cherry 3.4483%
Cottonwood 3.4483%
Cypress 3.4483%
Gum 3.4483%
Hackberry 3.4483%
Hard Maple 3.4483%
Hickory 3.4483%
Pecan 3.4483%
Poplan 3.4483%
Red Alder 3.4483%
Red Elm 3.4483%
Red Oak 6.8966%
Sassafras 3.4483%
Soft Maple 3.4483%
Sycamore 3.4483%
White Oak 10.3448%
Willow 3.4483%
Yellow Birch 3.4483%

思路:对map容器的使用:

#include
using namespace std;
#define maxn 1000010
map<string,int>mp;
int main(){
     
	int n;
	cin>>n;
	getchar();
	for( int i=0; i<n; i++ )
	{
     
		string str;
		getline(cin,str);
		mp[str]++;
	}
	for( map<string,int >::iterator it = mp.begin(); it!=mp.end(); it++ )
	{
      
		cout<<it->first<<' ';
		double num=0;
		num=it->second*100.0/n;//int乘100.0/int 为double
		printf("%.4f%\n",num);
	}
	return 0;
}

输出的时候注意int * 1.0 / int 类型 可以转化为double
4.20天梯模拟赛(并查集,搜索树)_第6张图片
这里区别一下set容器的遍历,输出的是* it:

for(set<string>::iterator it=s.begin();it!=s.end();it++)
        cout<<*it<<endl;

你可能感兴趣的:(笔记,数据结构,c++)