PAT-树与并查集

这篇博客详细介绍了PAT(A系列)中涉及的树形结构问题,包括如何根据后序和中序序列建树、判断图是否为树、求特定路径和最大高度等。同时,讲解了AVL树和并查集的应用,如求LCA、分类统计等。文章强调了在处理数据时使用map的重要性,并提供了关键代码示例。
摘要由CSDN通过智能技术生成

参考资料:《算法笔记》,柳神csdn博客

PAT-A1020

问题:给定后序和中序序列,要求给出层序序列。
思路:根据后序和中序序列建树然后再层序遍历输出即可。
关键:在建树过程中要考虑边界条件,即后序序列为空时if(preL>preR) return NULL 退出。

PAT-A1021

问题:给定一张图的边信息,如果这是一个树,给出deepest root(使树高最高的那个根),否则求出这图有几个连通分量。
思路:DFS
关键:用vector代替二维数组以避免TLE

#include<cstdio>
#include<vector>
using namespace std;
const int maxn = 10010;
vector<int> a[maxn];
int N,book[maxn],depth[maxn];
void dfs(int root){
	book[root] = 1;
	for(auto i:a[root]){
		if(book[i]==0){
			dfs(i);
		}
	}
}
vector<int> path;
void dfs2(int root){
	book[root] = 1;
	path.push_back(root);
	if(path.size()>depth[root])
		depth[root] = path.size();
	for(auto i:a[root]){
		if(book[i]==0)
			dfs2(i);
	}
	path.pop_back();
}
int main(){
	scanf("%d",&N);
	int p,q;
	for(int i=0;i<N;i++){
		scanf("%d%d",&p,&q);
		a[p].push_back(q);
		a[q].push_back(p);
	}
	int comp = 0;
	for(int i=1;i<=N;i++){
		if(book[i]==0){
			comp++;
			dfs(i);
		}
	}
	if(comp!=1){
		printf("Error: %d components",comp);
		return 0;
	}	
	for(int i=1;i<=N;i++){
		for(int j=1;j<=N;j++)
			book[j]=0;
		if(book[i]==0){
			dfs2(i);
		}
	}
	vector<int> v;
	int max=0;
	for(int i=1;i<=N;i++){
		if(depth[i]>max){
			max = depth[i];
			v.clear();
			v.push_back(i);
		}else if(depth[i]==max){
			v.push_back(i);
		}
	}
	for(int i=0;i<v.size();i++)
		printf("%d\n",v[i]);
	return 0;
}

PAT-A1053【hard】

问题:给定一个每个结点都有权值的树并给定一个整数S,要求求出从根到叶子的路径上所有结点权值之和为这个整数S的所有这样的路径,并按照权值降序排列输出。
思路:根据给定权值和信息建树,通过静态方式建树最方便,然后进行DFS来搜索。
关键:创建一个path变量用于记录当前搜索路径上的各个结点在书中的下标;创建一个与path变量对应的sum变量用于记录path路径上的权值之和,与给定的整数S进行比较以进行剪枝。DFS部分代码如下:

vector<int> path;//这里path用vector来实现
void DFS(int index,int sum){//index表示path中的最后一个结点的下标
	if(sum > S)
		return ;
	if(sum==S){
		if(Node[index].children.size()!=0)
			return;//不是叶子结点
		int numNode = path.size();
		for(int i=0;i<numNode;i++){
			printf("%d", Node[path[i]].weight);
			if(i<numNode-1)
				printf(" ");
			else
				printf("\n");
		}
		return;
	}
	//sum < S的情况
	for(int i=0;i<Node[index].children.size();i++){
		int child = Node[index].children[i];
		path.push_back(child);
		DFS(child,sum+Node[child].weight);
		path.pop_back();
	}

}

PAT-A1086

问题:给定先序序列以及通过栈来获得的中序序列,要求给出后序序列
思路:先通过栈模拟获得中序序列,然后根据先序和中序求后序。
关键:
1.cstring中的strcmp可以用来判断字符串。字符串用char a[12]这样来存储。
2.这里可以像A1020那样先建树,也可以直接求出。后者的关键部分代码如下:

void DFS(int root,int inL,int inR){
/*
在每次的DFS中:
	这里的root表示先序序列pre的根结点的下标
不需要建树,只需将每次得到的根结点记录即可。
*/
	if(inL > inR)	return;
	int k=inL;
	for(;k<=inR&&pre[root]!=in[k];k++);
	int numL = k - inL;
	DFS(root+1,inL,k-1);
	DFS(root+1+numL,k+1,inR);
	post.push_back(pre[root]);
}
//类似的,修改此处代码即可根据后序和中序得到先序,当前root也要从后往前。
DFS(root+1,inL,k-1);
DFS(root+1+numL,k+1,inR);
post.push_back(pre[root]);

疑惑:如何根据层序和中序获得后序?

PAT-A1102

问题:根据信息建树并给出inverted(按垂直轴旋转180度)后的树的层序和中序遍历
思路:建树然后lchild和rchild先后调换顺序即可
关键:对“-”的处理,对内存使用的限制

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 15;
struct node{
	int lchild;
	int rchild;
}Node[maxn];
queue<int> q;
int num1=0,num2=0,n;
void Level(int root){
	q.push(root);
	while(!q.empty()){
		int now = q.front();
		q.pop();
		printf("%d",now);
		if(++num1<n)
			printf(" ");
		if(Node[now].rchild!=-1)
			q.push(Node[now].rchild);
		if(Node[now].lchild!=-1)
			q.push(Node[now].lchild);
	}
}
void In(int root){
	if(root==-1)
		return;
	In(Node[root].rchild);
	printf("%d",root);
	if(++num2<n)
		printf(" ");
	In(Node[root].lchild);
}
int main(){
	scanf("%d",&n);
	string l,r;
	bool NotRoot[n]={false};
	for(int i=0;i<n;i++){
		cin >> l >> r;
		if(l!="-"){
			Node[i].lchild = stoi(l);
			NotRoot[Node[i].lchild] = true;
		}else{
			Node[i].lchild = -1;
		}
		if(r!="-"){
			Node[i].rchild = stoi(r);
			NotRoot[Node[i].rchild] = true;
		}else{
			Node[i].rchild = -1;
		}
	}
	int k;
	for(int i=0;i<n;i++){
		if(!NotRoot[i]){
			k = i;
			break;
		}
	}
	Level(k);
	printf("\n");
	In(k);
	return 0;
}

PAT-A1079

问题:在供应链的背景下,给出供应链上的树型关系以及货量,要求求出最终经销商的销售额。
思路:在用静态建树后遍历的同时记录下当前路径的depth即可。
关键:难度适中
一发AC代码:

#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 100000;
int N;
double P,r;
struct node{
	int w;
	vector<int> child;
}a[maxn];
double sum = .0;
void DFS(int root,int depth){
	if(a[root].child.empty()){
		sum += pow((1+r/100),depth-1)*P*a[root].w;
		return;
	}
	for(auto i:a[root].child){
		DFS(i,depth+1);
	}
}
int main(){
	scanf("%d%lf%lf",&N,&P,&r);
	int n,m;
	for(int i=0;i<N;i++){
		scanf("%d",&n);
		if(n==0){
			scanf("%d",&m);
			a[i].w = m;
			continue;
		}
		for(int j=0;j<n;j++){
			scanf("%d",&m);
			a[i].child.push_back(m);
		}
	}
	DFS(0,1);
	printf("%.1f",sum);
}

PAT-A1090

问题:与A1079类似
思路:反向思维,a的供应商是b,即b供应给a,用这样的思路来建树即可。
关键:难度适中
一发AC代码如下:

#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 100000;
int N,n,root,maxDepth=-1,maxNum=0;
double P,r;
vector<int> a[maxn];
void DFS(int root,int depth){
	if(a[root].empty()){
		if(depth>maxDepth){
			maxDepth = depth;
			maxNum = 1;
		}else if(depth==maxDepth){
			maxNum++;
		}
		return;
	}
	for(auto i:a[root]){
		DFS(i,depth+1);
	}
}
int main(){
	scanf("%d%lf%lf",&N,&P,&r);
	r/=100;
	for(int i=0;i<N;i++){
		scanf("%d",&n);
		if(n!=-1)
			a[n].push_back(i);
		else{
			root = i;
		}
	}
	DFS(root,1);
	double sum = pow(1+r,maxDepth-1)*P;
	printf("%.2f %d",sum,maxNum);
}

PAT-A1094

问题:给定一个族谱关系,要求求出一个每一个level的人数和中最大的那一个level以及这个数量。
思路:静态建树,一个DFS进行遍历的同时,加入level并统计。
关键:DFS时加入level参数。同时要注意在读取数据时,变量要一个个仔细对应上,不然浪费太多时间debug。

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 110;
vector<int> a[maxn];
int p[maxn]={0},N,M,maxP=0,maxL;
void DFS(int root,int level){
	p[level]++;
	for(int i=0;i<a[root].size();i++){
		DFS(a[root][i],level+1);
	}
} 
int main(){
	scanf("%d%d",&N,&M);
	int n,m,k;
	for(int i=0;i<M;i++){
		scanf("%d%d",&n,&m);
		for(int j=0;j<m;j++){
			scanf("%d",&k);
			a[n].push_back(k);
		}
	}
	DFS(1,1);
	for(int i=1;i<maxn;i++){
		if(p[i]>maxP){
			maxP = p[i];
			maxL = i;
		}
	}
	printf("%d %d",maxP,maxL);
	return 0;
}

PAT-A1130

问题:给一个二叉语法树要求给出其中缀表达式。
思路:建树,中序遍历求出中缀表达式即可。
关键:
1.用string来存储结点信息data
2.不要混用printf与cout 以及scanf与cin 不然会出错。
3.处理括号时可以举一些例子来帮助判断。

#include<iostream>
using namespace std;
const int maxn = 30;
struct node{
	string data;
	int lchild;
	int rchild;
}a[maxn];
int N,book[maxn];
void dfs(int root){
	if(root==-1)return;
	if(a[root].lchild==-1&&a[root].rchild==-1) {
		cout << a[root].data;
		return;
	}
	cout << "(";
	dfs(a[root].lchild);
	cout << a[root].data;
	dfs(a[root].rchild);
	cout << ")";
}
int main(){
	cin >> N;
	string str;
	int p,q,book[N+1]={0};
	for(int i=1;i<=N;i++){
		cin >> str >> p >> q;
		a[i].data = str;
		a[i].lchild = p;
		a[i].rchild = q;
		if(p!=-1)book[p]=1;
		if(q!=-1)book[q]=1;
	}
	int root=1;
	while(book[root]!=0)
		root++;
	dfs(a[root].lchild);
	cout << a[root].data;
	dfs(a[root].rchild);
	return 0;
}

PAT-A1106

与A1090是镜像问题、输入数据格式与A1079类似,稍加修改即可。

#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 100010;
int N,minDepth=maxn,minNum;
double P,r,sum=0;
vector<int> a[maxn];
void DFS(int root,int depth){
	if(a[root].empty()){
		if(depth<minDepth){
			minDepth = depth;
			minNum = 1;
		}else if(depth == minDepth){
			minNum++;
		}
		return;
	}
	for(int i=0;i<a[root].size();i++){
		DFS(a[root][i],depth+1);
	}
}
int main(){
	scanf("%d%lf%lf",&N,&P,&r);
	r/=100;
	int n,m;
	for(int i=0;i<N;i++){
		scanf("%d",&n);
		if(n!=0){
			for(int j=0;j<n;j++){
				scanf("%d",&m);
				a[i].push_back(m);
			}
		}	
	}
	DFS(0,1);
	double sum = pow(1+r,minDepth-1)*P;
	printf("%.4f %d",sum,minNum);
}

A1004

A1094稍加修改即可。

#include<iostream>
#include<vector>
using namespace std;
const int maxn = 110;
vector<int> a[maxn];
int p[maxn]={0},N,M,maxP=0,maxL;
void DFS(int root,int level){
	if(a[root].empty())
		p[level]++;
	for(int i=0;i<a[root].size();i++){
		DFS(a[root][i],level+1);
	}
	if(maxL<level){
		maxL = level;
	}
} 
int main(){
	scanf("%d%d",&N,&M);
	int n,m,k;
	for(int i=0;i<M;i++){
		scanf("%d%d",&n,&m);
		for(int j=0;j<m;j++){
			scanf("%d",&k);
			a[n].push_back(k);
		}
	}
	DFS(1,1);
	for(int i=1;i<=maxL;i++){
		if(i!=1)
			printf(" ");
		printf("%d",p[i]);
	}
	return 0;
}

A1043【hard】

问题:给出镜像BST的概念,并给出一个先序序列,要求判断这是否为一个BST或者镜像BST的先序序列,如果是,则还需要输出这个数的后序序列
思路:
1.模拟建树,根据题目给的先序序列建立一个BST,然后求这个BST的先序序列以及镜像先序序列,然后与题目给的序列比较来判断。
2.分析这个先序序列,按照BST的性质去求后序,如果长度不够则不是BST
关键:

#include<cstdio>
#include<vector>
using namespace std;
struct node{
	int data;
	node* lchild;
	node* rchild;
};
vector<int> pre,preM,post,postM,value;
const int maxn = 1010;
void insert(node* &root,int data){
	if(root==NULL){
		root = new node;
		root->data = data;
		root->lchild = root->rchild = NULL;
		return;
	}
	else if(data<root->data){
		insert(root->lchild,data);
	}else{
		insert(root->rchild,data);
	}
}
void preOrder(node* root){
	if(root==NULL)
		return;
	pre.push_back(root->data);
	preOrder(root->lchild);
	preOrder(root->rchild);
}
void preOrderM(node* root){
	if(root==NULL)
		return;
	preM.push_back(root->data);
	preOrderM(root->rchild);
	preOrderM(root->lchild);
}
void postOrder(node* root){
	if(root==NULL)
		return;
	postOrder(root->lchild);
	postOrder(root->rchild);
	post.push_back(root->data);
}
void postOrderM(node* root){
	if(root==NULL)
		return;
	postOrderM(root->rchild);
	postOrderM(root->lchild);
	postM.push_back(root->data);
}

int main(){
	int N,m;
	node* root = NULL;
	scanf("%d",&N);
	for(int i=0;i<N;i++){
		scanf("%d",&m);
		value.push_back(m);
		insert(root,m);
	}
	preOrder(root);
	preOrderM(root);
	if(value==pre){
		postOrder(root);
		printf("YES\n");
		for(int i=0;i<post.size();i++){
			if(i!=0)
				printf(" ");
			printf("%d",post[i]);
		}
	}else if(value==preM){
		postOrderM(root);
		printf("YES\n");
		for(int i=0;i<postM.size();i++){
			if(i!=0)
				printf(" ");
			printf("%d",postM[i]);
		}
	}else{
		printf("NO");
	}
	return 0;
}```


## 柳神
```cpp
#include <cstdio>
#include <vector>
using namespace std;
bool isMirror;
vector<int> pre, post;
void getpost(int root, int tail) {
    if(root > tail) return ;
    int i = root + 1, j = tail;
    if(!isMirror) {
        while(i <= tail && pre[root] > pre[i]) i++;
        while(j > root && pre[root] <= pre[j]) j--;
    } else {
        while(i <= tail && pre[root] <= pre[i]) i++;
        while(j > root && pre[root] > pre[j]) j--;
    }
    if(i - j != 1) return ;
    getpost(root + 1, j);
    getpost(i, tail);
    post.push_back(pre[root]);
}
int main() {
    int n;
    scanf("%d", &n);
    pre.resize(n);
    for(int i = 0; i < n; i++)
        scanf("%d", &pre[i]);
    getpost(0, n - 1);
    if(post.size() != n) {
        isMirror = true;
        post.clear();
        getpost(0, n - 1);
    }
    if(post.size() == n) {
        printf("YES\n%d", post[0]);
        for(int i = 1; i < n; i++)
            printf(" %d", post[i]);
    } else {
        printf("NO");
    }
    return 0;
}

A1064

问题:给出一个CBST(完全二叉查找树)的中序序列,要求求出其层序序列。
思路:可以建立一个数组用来存放完全二叉树。a是当前结点,则 2 a 2a 2a是左子树根结点, 2 a + 1 2a+1 2a+1是右子树根结点(若存在)。在对完全二叉树遍历时,用题目给的序列依次即可得到unique的CBST。
关键:完全二叉树数组本身从1到n遍历一遍得到的就是层序序列。题目给的序列不一定是从小到大排好序的,需要sort一下,sort默认升序。

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 1010;
int a[maxn],b[maxn],index_b=1,n;
vector<int> v;
void InOrder(int root){
	if(root>n)
		return;
	InOrder(root*2);
	a[root] = b[index_b++];
	InOrder(root*2+1);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&b[i]);
	sort(b+1,b+n+1);
	InOrder(1);
	for(int i=1;i<=n;i++){
		if(i!=1)
			printf(" ");
		printf("%d",a[i]);
	}
	return 0;
}

A1099

问题:给定BST的结构以及n个不同的数,要求求出这颗unique的BST的层序序列
思路:用结构体数组建树,先序遍历一遍把值赋上,然后再用queue求层序序列即可。
关键:对于题目给的n个不同的数要sort。输出结果部分可以在LevelOrder时一并完成,用一个num记录输出了多少个数,最后一个数不要输出空格即可。

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 110;
struct node{
	int data;
	int lchild;
	int rchild;
}a[maxn];
int n,b[maxn],index=0;
void InOrder(int root){
	if(root==-1)
		return;
	InOrder(a[root].lchild);
	a[root].data = b[index++];
	InOrder(a[root].rchild);
}
vector<int> v;
void LevelOrder(int root){
	queue<int> q;
	q.push(root);
	while(!q.empty()){
		int now = q.front();
		v.push_back(a[now].data);
		q.pop();
		if(a[now].lchild!=-1)
			q.push(a[now].lchild);
		if(a[now].rchild!=-1)
			q.push(a[now].rchild);
	}
}
int main(){
	scanf("%d",&n);
	int l,r;
	for(int i=0;i<n;i++){
		scanf("%d%d",&l,&r);
		a[i].lchild = l;
		a[i].rchild = r;
	}
	for(int i=0;i<n;i++)
		scanf("%d",&b[i]);
	sort(b,b+n);
	InOrder(0);
	LevelOrder(0);
	for(int i=0;i<v.size();i++){
		if(i!=0)
			printf(" ");
		printf("%d",v[i]);
	}
	return 0;
}

A1127

问题:给出中序序列和后序序列要求给出这个树的Zigzagging Order Sequence
思路:根据中序序列和后序序列建树,用两个栈倒来倒去即可。
关键:
1.考察会不会用中序序列和后序序列建树
2.考察对栈的理解
3.这里用两个栈来互相倒即可
4.这里两个栈的循环可以自己设定一个循环次数,如下代码所示
ac代码:

#include<cstdio>
#include<stack>
#include<vector>
using namespace std;
const int maxn = 40;
int N,in[maxn],post[maxn],index=0;
struct node {
	int data,level;
	node* lchild;
	node* rchild;
};
node* dfs(int inL,int inR,int postL,int postR){
	if(postL>postR)
		return NULL;
	node* root = new node;
	root->data = post[postR];
	int k = inL;
	while(k<=inR&&in[k]!=root->data)k++;
	int numL = k-inL;
	root->lchild = dfs(inL,k-1,postL,postL+numL-1);
	root->rchild = dfs(k+1,inR,postL+numL,postR-1);
	return root;
}
void dfs2(node* root,int level){
	if(root==NULL)
		return;
	root->level = level;
	dfs2(root->lchild,level+1);
	dfs2(root->rchild,level+1);
}
int main(){
	scanf("%d",&N);
	for(int i=0;i<N;i++)
		scanf("%d",&in[i]);
	for(int i=0;i<N;i++)
		scanf("%d",&post[i]);
	node* root = dfs(0,N-1,0,N-1);
	dfs2(root,1);
	stack<node*> s1;//开两个栈,如果只用一个栈会造成最近压入的结果立刻输出 
	stack<node*> s2;
	vector<node*> v;
	s1.push(root);
	int skr = 100;
	while(skr-->0){//可以直接暴力设定一个循环范围 
		while(!s1.empty()){
			node* now = s1.top();
			v.push_back(now);
			s1.pop();
			if(now->rchild!=NULL)s2.push(now->rchild);
			if(now->lchild!=NULL)s2.push(now->lchild);
		}
		while(!s2.empty()){
			node* now = s2.top();
			v.push_back(now);
			s2.pop();
			if(now->lchild!=NULL)s1.push(now->lchild);
			if(now->rchild!=NULL)s1.push(now->rchild);
		} 
	}
	for(int i=0;i<v.size();i++){
		if(i!=0)
			printf(" ");
		printf("%d",v[i]->data);
	}
	return 0;
}

也可以判断一下,当栈s2 push完后,s1为空时显然应该break

while(true){
		while(!s1.empty()){
			node* now = s1.top();
			v.push_back(now);
			s1.pop();
			if(now->rchild!=NULL)s2.push(now->rchild);
			if(now->lchild!=NULL)s2.push(now->lchild);
		}
		while(!s2.empty()){
			node* now = s2.top();
			v.push_back(now);
			s2.pop();
			if(now->lchild!=NULL)s1.push(now->lchild);
			if(now->rchild!=NULL)s1.push(now->rchild);
		} 
		if(s1.empty())
			break;
	}

A1115【注意审题!】

问题:给出一组序列,按序插入BST中,然后求这个BST最深两层的结点数。
思路:常规建BST。遍历一遍统计出每层的结点数
关键:
注意审题,这里题目中说BST结点左边是小于或等于,右边是大于。与常规想法不同,所以审题很重要!

#include<cstdio>

using namespace std;
const int maxn = 1010;
int N;
struct node{
	int data,level;
	node* lchild;
	node* rchild;
};
void insert(node* &root,int data){
	if(root==NULL){
		root = new node;
		root->data = data;
		root->lchild=root->rchild=NULL;
		return;
	}
	if(data<=root->data)
		insert(root->lchild,data);
	else if(data>root->data)
		insert(root->rchild,data);
}
int maxL = 0;
void dfs(node* root,int level){
	if(root==NULL)
		return;
	if(maxL<level)
		maxL = level;
	root->level = level;
	dfs(root->lchild,level+1);
	dfs(root->rchild,level+1);
}
int n1=0,n2=0;
void find(node* root){
	if(root->level==maxL)
		n1++;
	if(root->level==(maxL-1))
		n2++;
	if(root->lchild!=NULL)
		find(root->lchild);
	if(root->rchild!=NULL)
		find(root->rchild);
}
int main(){
	scanf("%d",&N);
	node* root = NULL;
	int m;
	for(int i=0;i<N;i++){
		scanf("%d",&m);
		insert(root,m);
	}
	dfs(root,1);
	find(root);
	printf("%d + %d = %d",n1,n2,n1+n2);
	return 0;
}

A1138

问题:题目给出一个树的先序序列和中序序列,要求求出后序序列的第一个数
思路:老老实实求就完事了。
关键:
1.要求后序序列的第一个,那么老实点求后序序列即可,不用搞什么一步到位花里胡哨。

#include<cstdio>
#include<vector>
using namespace std;
const int maxn = 50010;
struct node{
	int data;
	node* lchild;
	node* rchild;
};
int pre[maxn],in[maxn],N;
node* dfs(int preL,int preR,int inL,int inR){
	if(inL>inR)
		return NULL;
	node* root = new node;
	root->data = pre[preL];
	int k = inL;
	while(k<=inR&&in[k]!=root->data)k++;
	int numL = k - inL;
	root->lchild = dfs(preL+1,preL+numL,inL,k-1);
	root->rchild = dfs(preL+numL+1,preR,k+1,inR);
	return root;
}
vector<int> v;
void postOrder(node* root){
	if(root==NULL)
		return;
	postOrder(root->lchild);
	postOrder(root->rchild);
	v.push_back(root->data);
}
int main(){
	scanf("%d",&N);
	for(int i=0;i<N;i++)
		scanf("%d",&pre[i]);
	for(int i=0;i<N;i++)
		scanf("%d",&in[i]);
	node* root = dfs(0,N-1,0,N-1);
	while(root->lchild!=NULL)
		root = root->lchild;
	postOrder(root);
	printf("%d",v[0]);
	return 0;
}

A1110【hard | review:dfs部分要细品】

问题:判断一棵树是否为完全二叉树
思路:新建一个数组,用1表示根结点,如果k是根,那么 2 k 2k 2k存左子节点, 2 k + 1 2k+1 2k+1存右子节点,然后判断这个数组的有效大小是否为总个数-1即可。
关键:
1.这里要用string去读,如果用char那么只能处理一位数的情况,很容易出错误。
2.string的"=="比较很好用,stoi函数很好用
3.上述思路并需要真的用一个数组去存这些点,只需要点的原下标与我们设定的这个数组的下标对应上即可。

#include<iostream>
// #include<string>
using namespace std;
const int maxn = 30;
int N,book[maxn],maxIndex=-1,maxRoot;
struct node{
	int lchild;
	int rchild;
}a[maxn];
void dfs(int root,int index){
	if(root==-1)
		return;
	if(maxIndex<index){
		maxIndex = index;
		maxRoot = root;
	}
	dfs(a[root].lchild,index*2);
	dfs(a[root].rchild,index*2+1);
}

int main(){
	cin >> N;	
	string s1,s2;
	for(int i=0;i<N;i++){
		cin >> s1 >> s2;
		if(s1=="-"){
			a[i].lchild = -1;
		}else{
			a[i].lchild = stoi(s1);
			book[stoi(s1)] = 1;
		}
		if(s2=="-"){
			a[i].rchild = -1;
		}else{
			a[i].rchild = stoi(s2);
			book[stoi(s2)] = 1;
		}
	}
	int root = 0;
	for(;root<N;root++){
		if(book[root]!=1)
			break;
	}
	dfs(root,1);
	if(maxIndex==N){
		printf("YES %d",maxRoot);
	}else{
		printf("NO %d",root);
	}
}

A1143【disgusting | 防一手】

问题:给一个BST的先序序列,求LCA(Lowest Common Ancestor)
思路:用一个map去记录有效结点,不需要建树因为BST的根的值肯定在它的子结点(两个要求的点)的值之间,或者根就是其中一个要求的点。
关键:
被这题的样本点真的恶心坏了,如果用数组去记录各个输入的点,无论开的多大都会有一个点通不过。
在这里插入图片描述
在这里插入图片描述
必须得用map<int,bool>去做才能全部通过,醉了(反正如果真在考试的时候我是想不到会在这种地方出问题)

防一手 → \rightarrow 在做记录时常用map,少用数组,切记切记。

在这里插入图片描述

在这里插入图片描述

#include<cstdio>
#include<vector>
#include<map>
using namespace std;
int M,N;
map<int,bool> book;
int main(){
	int m,u,v,a;
	scanf("%d %d",&M,&N);
	vector<int> pre;
	for(int i=0;i<N;i++){
		scanf("%d",&m);
		pre.push_back(m);
		book[m]=true;
	}
	for(int i=0;i<M;i++){
		scanf("%d %d",&u,&v);
		if(!book[u]&&!book[v]){
			printf("ERROR: %d and %d are not found.\n",u,v);
			continue;
		}
		else if(!book[u]||!book[v]){
			printf("ERROR: %d is not found.\n",book[u]?v:u);
			continue;
		}
		for(int j=0;j<pre.size();j++){
			a = pre[j];
			if((a>u&&a<v)||(a>v&&a<u)||(a==u)||(a==v))
				break;
		}
		if(a==u||a==v)
			printf("%d is an ancestor of %d.\n",a,(a==u)?v:u);
		else
			printf("LCA of %d and %d is %d.\n",u,v,a);
	}
	return 0;
}

AVL

A1066【hard】

问题:题目给出AVL树各个结点值的插入顺序,要求求出该树的根的值为多少。
思路:按常规的做就行,主要看有没有复习到AVL。
关键:直接输出中位数可以嫖到不少分。
在这里插入图片描述

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 30;
int n,a[maxn];
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	sort(a,a+n);
	printf("%d",a[n/2]);
}

一发AC

#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
	int data;
	int height;
	node* lchild;
	node* rchild;
};
const int maxn = 30;
int n,a[maxn];
node* create(int data){
	node* root = new node;
	root->data = data;
	root->height = 1;
	root->lchild = NULL;
	root->rchild = NULL;
	return root;
}
int getHeight(node* root){
	if(root==NULL)
		return 0;
	return root->height;
}
void UpdateHeight(node* &root){
	root->height = max(getHeight(root->lchild),getHeight(root->rchild)) + 1;
}
int getBF(node* root){
	return getHeight(root->lchild) - getHeight(root->rchild);
}
void L(node* &root){
	node* temp = root->rchild;
	root->rchild = temp->lchild;
	temp->lchild = root;
	UpdateHeight(root);
	UpdateHeight(temp);
	root = temp;
}
void R(node* &root){
	node* temp = root->lchild;
	root->lchild = temp->rchild;
	temp->rchild = root;
	UpdateHeight(root);
	UpdateHeight(temp);
	root = temp;
}
void insert(node* &root,int data){
	if(root==NULL){
		root = create(data);
		return;
	}
	if(data < root->data){
		insert(root->lchild,data);
		UpdateHeight(root);
		if(getBF(root)==2){
			if(getBF(root->lchild)==1){//LL
				R(root);
			}else if(getBF(root->lchild)==-1){//LR
				L(root->lchild);
				R(root);
			}
		}
	}else if(data > root->data){
		insert(root->rchild,data);
		UpdateHeight(root);
		if(getBF(root)==-2){
			if(getBF(root->rchild)==1){//RL
				R(root->rchild);
				L(root);
			}else if(getBF(root->rchild)==-1){//RR
				L(root);
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	node* root = create(a[0]);
	for(int i=1;i<n;i++)
		insert(root,a[i]);
	printf("%d",root->data);
	return 0;
}

并查集

路径压缩的递归式的findFather
int findFather(int v){
	if(v==father[v])
		return v;
	else{
		int f = findFather(father[v]);//为了让递归动起来所以这里需要father[v]
		father[v] = f;
		return f;
	}
}

A1107【hard】

问题:n个人有自己不同的hobby,有相同hobby的人是一类,根据题目给出的hobby情况求出这些人可以分为几类
思路:创建一个hobby[k],k表示hobby的index,hobby[k]表示这个hobby的代表者,如果其他人也有类似的hobby k,则将这个人与代表者Union起来。这样我们就得到了并查集father[],然后根据并查集进行统计,这部分是有固定格式的:

int group[n] = {0};
for(int k=1;k<n;k++)
	group[father[k]]++;

关键:
1.数组开大点没事的,反正操作都不是很复杂
2.将数组的下标赋予意义
3.sort 降序排序

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1010;
int N,father[maxn],course[maxn]={0},group[maxn]={0};
void init(int n){
	for(int i=1;i<=n;i++){
		father[i] = i;
	}
}
int findFather(int a){
	if(a==father[a])
		return a;
	int F = findFather(father[a]);
	father[a] = F;
	return F;
}
void Union(int a,int b){
	int f1 = findFather(a);
	int f2 = findFather(b);
	if(f1!=f2)
		father[f1] = f2;
}
bool cmp(int a,int b){
	return a>b;
}
int main(){
	scanf("%d",&N);
	init(N);
	int n,m;
	for(int i=1;i<=N;i++){
		scanf("%d: ",&n);
		for(int j=0;j<n;j++){
			scanf("%d",&m);
			if(course[m]==0)
				course[m] = i;
			else{
				Union(i,course[m]);
			}
		}
	}
	for(int i=1;i<=N;i++){
		group[findFather(i)]++;
	}
	int num=0;
	for(int i=1;i<=N;i++){
		if(group[i]!=0)
			num++;
	}
	sort(group+1,group+N+1,cmp);
	printf("%d\n%d",num,group[1]);
	for(int i=2;i<=num;i++){
		printf(" %d",group[i]);
	}
	return 0;
}

A1118

和A1107类似,开一个course[]用于记录一张照片的代表者。得到并查集father[]后开一个book记录一下father的bool情况即可。最后判断两个鸟是否是同一个树用一个findFather==findFather判断即可。

#include<cstdio>

using namespace std;
const int maxn = 10010;
int N,maxNum=0,father[maxn]={0},course[maxn]={0},book[maxn]={0};
int findFather(int a){
	if(a==father[a])
		return a;
	int F = findFather(father[a]);
	father[a] = F;
	return F;
}
void init(){
	for(int i=0;i<maxn;i++)
		father[i] = i;
}
void Union(int a,int b){
	int f1 = findFather(a);
	int f2 = findFather(b);
	father[f1] = f2;
}
int main(){
	init();
	int n,m;
	scanf("%d",&N);
	for(int i=0;i<N;i++){
		scanf("%d",&n);
		for(int j=0;j<n;j++){
			scanf("%d",&m);
			if(course[i]==0)
				course[i]=m;
			else{
				Union(m,course[i]);
			}
			if(m>maxNum)
				maxNum = m;
		}
	}
	for(int i=1;i<=maxNum;i++){
		book[findFather(i)] = 1;
	}
	int sum = 0;
	for(int i=1;i<=maxNum;i++){
		sum+=book[i];
	}
	printf("%d %d\n",sum,maxNum);
	int _n;
	scanf("%d",&_n);
	int _a,_b;
	for(int i=0;i<_n;i++){
		scanf("%d %d",&_a,&_b);
		if(findFather(_a)==findFather(_b))
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}

A1034【hard | review】

问题:根据多个通话记录判断有几个团伙以及他们的老大和人数。
思路:并查集,也可以用图论DFS去做
关键:未ac 看了其他人的代码发现自己好像理解错误了,题目中说人的姓名由A-Z的三个大写字母组成,我理解为AAA一直到ZZZ就这么2个人了 /wul。所以这种一一对应的还是要用map,不要取巧。
用并查集的代码:

#include<iostream>
#include<map>
#include<vector>
using namespace std;
const int maxn = 2010;
map<string,int> stringToint;
map<int,string> intTostring;
int N,K,father[maxn],head[maxn]={0},weight[maxn]={0},total[maxn]={0},peopleNumber[maxn]={0},book[maxn]={0};
void init(){
	for(int i=0;i<maxn;i++)
		father[i] = i;
}
int find(int x){
	if(x==father[x])
		return x;
	int f = find(father[x]);
	father[x] = f;
	return f;
}
void Union(int x,int y){
	father[find(x)] = find(y);
}
int idNumber = 1;
int stoifunc(string str){
	if(stringToint[str]==0){
		stringToint[str] = idNumber;
		intTostring[idNumber] = str;
		return idNumber++;
	}else{
		return stringToint[str];
	}
}
int main(){
//	freopen("1.txt","r",stdin);
	init();
	scanf("%d %d",&N,&K);
	string str_a,str_b;
	int w;
	for(int i=0;i<N;i++){
		cin >> str_a >> str_b >> w;
		int id1 = stoifunc(str_a);
		int id2 = stoifunc(str_b);
		weight[id1]+=w;
		weight[id2]+=w;
		Union(id1,id2);
	}
	//统计gang的总人数和总时长
	for(int i=1;i<idNumber;i++){
		peopleNumber[find(i)]++;
		total[find(i)]+=weight[i];
		//统计gang中的head
		if(head[find(i)]==0)
			head[find(i)] = i;
		if(weight[i]>weight[head[find(i)]]){
			head[find(i)] = i;
		}
	}
	map<string,int> gang;
	for(int i=1;i<=idNumber;i++){
		if(peopleNumber[i]>2&&(total[i]/2)>K)
			gang[intTostring[head[i]]] = peopleNumber[i];
	}
	cout << gang.size() << endl;
	map<string,int>::iterator it;
	for(it=gang.begin();it!=gang.end();it++){
		cout << it->first << " " << it->second << endl;
	}
	return 0;
}

用图论、DFS的代码:

#include<iostream>
#include<map>
#include<vector>
using namespace std;
const int maxn = 2010;
map<string,int> stringToint;
map<int,string> intTostring;
int N,K,G[maxn][maxn],vis[maxn],weight[maxn];
int idNumber = 1;
int stoifunc(string str){
	if(stringToint[str]==0){
		stringToint[str] = idNumber;
		intTostring[idNumber] = str;
		return idNumber++;
	}else{
		return stringToint[str];
	}
}
void dfs(int root,int &head,int &peopleNumber,int &total){
	vis[root] = 1;
	peopleNumber++;
	if(weight[root] > weight[head])
		head = root;
	for(int i=1;i<idNumber;i++){
		if(G[root][i] > 0){//对于那些还没访问过边的(无论是否vis因为可能有环)
			total += G[root][i];
			G[root][i] = 0;//归零 防止重复访问
			G[i][root] = 0;
			if(!vis[i])
				dfs(i,head,peopleNumber,total);
		}
	}
}
int main(){
	scanf("%d %d",&N,&K);
	string str_a,str_b;
	int w;
	for(int i=0;i<N;i++){
		cin >> str_a >> str_b >> w;
		int id1 = stoifunc(str_a);
		int id2 = stoifunc(str_b);
		weight[id1]+=w;
		weight[id2]+=w;
		G[id1][id2] += w;
		G[id2][id1] += w;
	}
	map<string,int> gang;
	int peopleNumber,total,head;
	for(int i=1;i<idNumber;i++){
		if(!vis[i]){
			head = i;
			peopleNumber = 0;
			total = 0;
			dfs(i,head,peopleNumber,total);//访问i所在的连通块
			if(peopleNumber>2&&total>K)
				gang[intTostring[head]] = peopleNumber;
		}
	}
	cout << gang.size() << endl;
	for(auto it=gang.begin();it!=gang.end();it++){
		cout << it->first << " " << it->second << endl;
	}
}

A1114【hard | complex | review】

问题:给了一堆关于家庭人口、房产、房产面积的信息,根据要求输出
思路:结构体数组+并查集
关键:
1.对于种类繁杂的信息要学会用结构体去存储
2.对于房产数和房产面积的处理要在并查集建立完毕后再处理,因为在建立的过程中每个家族的father根结点会不断移动。

3.因为题目中要求家族最小的id,所以在Union时可以做一个判断,将更小的id作为father【细品】

4.统计各个家族的总人数时应该要用全部遍历一遍的方式去做,如果一个结构体一个结构体的找中间一些重复的人会被计入。
AC代码:

#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
const int maxn = 10010;
int N,father[maxn],people[maxn];
double estate[maxn],area[maxn];
struct data{
	int id,fid,mid,k;
	int childid[10];
	int estate,area;
	bool flag;
};
void init(){
	for(int i=0;i<maxn;i++)
		father[i] = i;
}
int find(int x){
	if(x == father[x])
		return x;
	int f = find(father[x]);
	father[x] = f;
	return f;
}
void Union(int x,int y){//题目中要求最小id,因此用更小的id作为father
	int f1 = find(x);
	int f2 = find(y);
	if(f1<f2)
		father[f2] = f1;
	if(f2<f1)
		father[f1] = f2;
}
bool cmp(int x,int y){
	if(area[x]==area[y])
		return x < y;
	return area[x] > area[y];
}
int main(){
	scanf("%d",&N);
	data a[N];
	init();
	int k;
	for(int i=0;i<N;i++){
		scanf("%d %d %d %d",&a[i].id,&a[i].fid,&a[i].mid,&a[i].k);
		if(a[i].fid!=-1)
			Union(a[i].fid,a[i].id);
		if(a[i].mid!=-1)
			Union(a[i].mid,a[i].id);
		for(int j=0;j<a[i].k;j++){
			scanf("%d",&a[i].childid[j]);
			Union(a[i].childid[j],a[i].id);
		}
		scanf("%d %d",&a[i].estate,&a[i].area);
	}
	set<int> st;
	for(int i=0;i<N;i++){
		int f = find(a[i].id);
		st.insert(f);
		estate[f] += (double)a[i].estate;
		area[f] += (double)a[i].area;
	}
	for(int i=0;i<10000;i++){
		people[find(i)]++;
	}
	for(auto f:st){
		estate[f] /= people[f];
		area[f] /= people[f];
	}
	vector<int> v;
	v.assign(st.begin(),st.end());
	sort(v.begin(),v.end(),cmp);
	printf("%d\n",v.size());
	for(int i=0;i<v.size();i++){
		int id = v[i];
		printf("%04d %d %.3f %.3f\n",id,people[id],estate[id],area[id]);
	}
	return 0;
}

A1098【hard】

问题:给出一个初始序列,一个部分排好序的序列,要求判断这个序列是InsertionSort or heapSort并输出该排序模式的下一步序列结果。
思路:
1、两种排序都试一遍,然后比较得出是哪一种排序,然后输出下一步的序列结果。
2、有分析可知,插入排序的前几个都是有序的;堆排序的后几个都是有序的,可以根据这个特性来判断。
关键:
1、插入排序的部分如果要模拟,可以直接用sort。
2、这道题中说的是中间序列“ partially sorted sequence”,所以初始序列是不考虑的。如下例所示:

//input
4
3 4 2 1
3 4 2 1
//output
Insertion Sort
2 3 4 1

3、对于函数参数写法:

func(int* a)func(int a[])是等价的,都是以指针参数,而不是将整个数组复制一遍。

4、在模拟时,如果从初始序列开始模拟,那么HeapSort需要先建堆,即对 N 2 − 1 \frac{N}{2} - 1 2N1都要做一次调整。
5、头文件中的std::swap可以直接调用,swap可以理解是通过指针交换的,但是不需要取地址。
在这里插入图片描述

通过模拟的方式ac的代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 110;
int N,a[maxn],target[maxn];
bool isSame(int a[],int b[]){
	for(int i=1;i<=N;i++){
		if(a[i]!=b[i])
			return false;
	}
	return true;
}
void showArray(int a[]){
	for(int i=1;i<=N;i++){
		if(i!=1)
			printf(" ");
		printf("%d",a[i]);
	}
}
void InsertionSort(int origin[]){
	bool flag = false;
	for(int i=2;i<=N;i++){
		if(i!=2&&isSame(origin,target)){
			flag = true;
		}
		sort(origin+1,origin+i+1);
		if(flag){
			printf("Insertion Sort\n");
			showArray(origin);
			return;
		}
	}
}
void swap(int *a,int *b){
	int c = *a;
	*a = *b;
	*b = c;
}
void DownAdjust(int* origin,int low,int high){
	int i = low, j = 2*i;
	while(j<=high){
		if(j+1<=high&&origin[j+1]>origin[j])
			j++;
		if(origin[i]<origin[j]){
			swap(&origin[i],&origin[j]);
			i = j;
			j = 2*i;
		}else{
			break;
		}
	}
}
void HeapSort(int origin[]){
	bool flag = false;
	//先建堆
	for(int i=N/2;i>=1;i--)
		DownAdjust(origin,i,N);
	for(int i=N;i>1;i--){
		//i!=N以保证是中间过程
		if(i!=N&&isSame(origin,target)){
			flag = true;
		}
		swap(origin+1,origin+i);
		DownAdjust(origin,1,i-1);
		if(flag){
			printf("Heap Sort\n");
			showArray(origin);
			return;
		}
	}
}
int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=N;i++)
		scanf("%d",&target[i]);
	int b[maxn];
	for(int i=1;i<=N;i++)
		b[i] = a[i];
	InsertionSort(a);
	HeapSort(b);
	return 0;
}

通过特性来判断的ac的代码:

这里online judge 时有两处通不过:
1.对于下标k和p退出循环后的位置不够清晰
2.这里越过前面的有序子序列时,条件应该为b[k]<=b[k+1],挺坑的,但是实际上很好理解,因为这道题中初始序列是不考虑的,下举例子说明:

6
3 3 2 1 0 4  a[]
2 3 3 1 0 4  b[]
//output
1 2 3 3 0 4

如果不越过相邻相等的情况并且这是一个Insertion Sort 那么输出的结果和b一样,显然是错误的。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 110;
int N,a[maxn],b[maxn];
void downAdjust(int low,int high){
	int i=low,j=i*2;
	while(j<=high){
		if(j+1<=high&&b[j+1]>b[j])
			j++;
		if(b[i]<b[j]){
			swap(b[i],b[j]);
			i = j;
			j = i*2;
		}else{
			break;
		}
	}
}
int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=N;i++)
		scanf("%d",&b[i]);
	int k = 1;
	while(k<=N&&b[k]<=b[k+1])//如果是Insertion,那么越过前面有序子序列
		k++;
	k++;
	int index = k;
	while(k<=N&&a[k]==b[k])//如果是Insertion,那么后面子序列应该没有变动
		k++;
	if(k==(N+1)){
		printf("Insertion Sort\n");
		sort(b+1,b+index+1);
	}else{
		printf("Heap Sort\n");
		/*
		说明是HeapSort
		这里的b已经是一个HeapSort的中间过程	
		*/
		int p = N;
		while(p>=1&&b[p-1]<b[p])
			p--;
		swap(b[1],b[p]);
		downAdjust(1,p-1);
	}
	for(int i=1;i<=N;i++){
		if(i!=1)
			printf(" ");
		printf("%d",b[i]);
	}
}

哈夫曼树

priority_queue的用法

//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;
//对于基础类型 默认是大顶堆
priority_queue<int> a; 
//等同于 priority_queue<int, vector<int>, less<int> > a;

//             这里一定要有空格,不然成了右移运算符↓
priority_queue<int, vector<int>, greater<int> > c;  //这样就是小顶堆
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值