文章首发于:My Blog 欢迎大佬们前来逛逛
P4715 【深基16.例1】淘汰赛
根据输入的n便可以得知:共有 2^n 个节点。
我们需要求得亚军是谁,那么我们就是要求第二个是谁。
冠军是谁?
4 2 3 1 10 5 9 7
假如把这个序列左右部分切开,那么冠军是一定是左边最大,右边最大的最大的那一个。
这样我们就可以看出一个树的结构:
依次比较两个节点的值,求出两个节点的较大者为他们两个的“父节点”
这样依次往上,从叶子节点到根节点,最终还剩两个节点,则较小的就是亚军。
注意我们得到的是节点的值,但是我们需要得到这个节点的编号,因此使用一个节点体记录每个节点的编号与值
//TODO: Write code here
int n,m;
const int N=1e5+10;
struct Node
{
int num,val;
}node[N];
signed main()
{
cin>>n;
int ans=0;
int len=pow(2,n);
for (int i=1;i<=len;i++){
cin>>node[i].val;
node[i].num=i;
}
while (len!=1){
if (len==2){//直到最后还剩两个节点
if (node[1].val>=node[2].val){
ans=node[2].num;
}else{
ans=node[1].num;
}
break;
}
for (int i=1,j=1;i<=len;i+=2,j++){
if (node[i].val>=node[i+1].val){
node[j]=node[i];
}
else{
node[j]=node[i+1];
}
}
len>>=1;
}
cout<<ans;
#define one 1
return 0;
}
P4913 【深基16.例3】二叉树深度
直接递归遍历这棵二叉树,同时统计一个cnt,每进入一次递归,则cnt+1,最后到达null节点,则ans统计这个时候的 cnt的最大值。
还有没有另外的做法?
扩展:求深度也可以使用最短路径来求,例如 使用 floyd算法得到 根节点到每个节点 的距离,规定根节点到与之相邻孩子节点的距离是1,则如果这个 dp[1] [i] 的值是存在的,则统计 1 -> i的距离的最大值
//TODO: Write code here
int n,m;
const int N=1e5+10;
int nums[N],ans;
struct Node
{
int l,r;
}node[N];
void dfs(int i,int cnt){
if (node[i].l==0 && node[i].r==0){
ans=max(ans,cnt);
return;
}
dfs(node[i].l,cnt+1);
dfs(node[i].r,cnt+1);
}
signed main()
{
cin>>n;
for (int i=1;i<=n;i++){
cin>>node[i].l>>node[i].r;
}
dfs(1,1);
cout<<ans;
#define one 1
return 0;
}
P1827 中序与前序,求出后序
P1827 [USACO3.4] 美国血统 American Heritage
前序遍历:根左右
中序遍历:左根右
如果得到了 根节点,则我们就能推出一个分治的过程,根节点的左边是左孩子部分,根节点的右边是右孩子部分,其中每个左右孩子部分又可以得到根节点,然后重复这一过程:
这里介绍STL的方法:
find(root); //返回root的位置,返回值是 size_t 的值,从0开始,如果在头部找到,则返回0
erase(xxx);//删除某一个位置
substr(beg,len)//从beg开始,截取len个元素
substr(beg); //只有一个beg位置,则截取beg到end的后面的所有元素
void dfs1(string mid,string pre){
/*
前序中序 -> 后序
*/
if (mid.size()==0 || pre.size()==0){
return;
}
char root=pre.front();//前序遍历的根节点
pre.erase(pre.begin());//删除pre的根节点
auto root_it=mid.find(root); //中序遍历寻找此根节点
//寻找中序遍历的left部分和right部分
string mid_left=mid.substr(0,root_it);
string mid_right=mid.substr(root_it+1);
//寻找前序遍历的left部分和right部分
string pre_left=pre.substr(0,root_it);
string pre_right=pre.substr(root_it);
//后序:左右根形式输出
dfs1(mid_left,pre_left);//左
dfs1(mid_right,pre_right);//右
cout<<root;//根
}
P1030 中序与后序,求出前序
后序遍历:左右根
中序遍历:左根右
只要知道了前序遍历或者后序遍历,再加上中序遍历,就可以推出另一种形式。
但是请注意:后序遍历和前序遍历是无法得到一个唯一的中序遍历的。
这里使用STL的方法:
void dfs2(string mid,string last){
/*
后序中序 -> 前序
*/
if (mid.size()==0 || last.size()==0){
return;
}
char root=last.back();//后序遍历的根节点
last.pop_back(); //删除last的根节点
auto root_it=mid.find(root);
//中序的left部分和right部分
string center_left=mid.substr(0,root_it);
string center_right=mid.substr(root_it+1);
//后序的left部分和right部分
string last_left=last.substr(0,root_it);
string last_right=last.substr(root_it);
//前序形式输出:根左右
cout<<root;//根
dfs2(center_left,last_left);//左
dfs2(center_right,last_right);//右
}
P1229 前序和后序,求出所有中序的次数
前面我们说过:由前序遍历和后序遍历无法确定一个唯一的中序遍历
这道题就是让我们求中序遍历的所有可能的数量
假如一个节点A只有一个孩子节点B,则思考一下:无论B是A的左孩子还是右孩子,它的前序遍历和后序遍历都是一致的。
因此如果要找到不同的中序遍历的情况,则一定是存在上述的情况。
A的孩子节点B只存在一个,这样就能确定两种情况;
如果A也是它的父亲节点 X 的唯一的孩子,那么A的左右改变又可以确定两种情况,加上A自己的,一共有四种情况。
- 一颗树存在两个单孩子节点,则这棵树就具有四种中序,即具有 2^ n个 中序的情况,n表示单孩子节点的个数
如何确定一个单孩子节点?
前序遍历:ABC
后序遍历:CBA
假设它是一个一条直线,左斜的形状
- 则A的孩子节点B在前序中呈:AB,在后序中呈:BA
- 则B的孩子节点C在前序中呈:BC,在后序中呈:CB
则仅需判断在中序遍历中A的后继是B,如果在后序遍历中A的前驱是B,则说明为一个情况,n++
最后 2^n即为中序的所有次数。
//TODO: Write code here
int n,m;
const int N=1e5+10;
int nums[N];
signed main()
{
string q,h;
cin>>q>>h;
int ans=0;
for (int i=0;i<q.length();i++){
for (int j=0;j<h.length();j++){
//q[i]==h[j] && q[i+1]==h[j-1]
if (i+1<q.length() && j-1>=0 && q[i]==h[j] && q[i+1]==h[j-1]){
++ans;
}
}
}
cout<<(1<<ans);
#define one 1
return 0;
}
P1364 医院设置
这道题的是一个典型的求段路径问题。
要使得在一个节点处建立医院,使得其他节点到这个节点的最短路径最小,不就是求最短路径?
每两个节点的距离是1,因此求出这个最短距离后再乘以节点本身的值然后相加即可
最短路径的求法: floyd算法,dijkstra算法,spfa算法;
这里附上floyd算法:floyd使用三重循环,求得任何两个点的最短路径。
//TODO: Write code here
int n,m;
const int N=5e2+10;
int nums[N],dp[N][N];
void floyd(){
for (int k=1;k<=n;k++){
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
}
}
}
}
signed main()
{
cin>>n;
memset(dp,0x3f,sizeof(dp));
for (int i=1;i<=n;i++){
int b,c;
cin>>nums[i]>>b>>c;
//两点之间距离的初始化
dp[i][b]=dp[i][c]=dp[b][i]=dp[c][i]=1;
dp[b][b]=dp[i][i]=dp[c][c]=0;
}
//计算两点之间的最短路径
floyd();
int ans=INF;
for (int i=1;i<=n;i++){
int ones=0;
//以顶点 i 为起点,到某一点的最小距离和
for (int j=1;j<=n;j++){
ones+=dp[i][j]*nums[j];
}
ans=min(ans,ones);
}
cout<<ans;
#define one 1
return 0;
}
* P1305 新二叉树
这道题的关键是选择合适的数据结构来存储这棵树,输出树的前序遍历很简单
如果选择合适的数据结构?
- 由于树的节点不会有重复的,n<=26,则我们使用ASCII码来表示下标,因为ASCII码也是唯一的 ,而且很小,我们可以借助ASCII码当作数组的下标来轻松访问其相应的左右孩子节点,使用结构体存储left与right孩子,pair也行。
1. Ascii码用作数组的下标
//TODO: Write code here
int n,m;
const int N=1e5+10;
int nums[N];
struct Tree
{
char left,right;
}tree[N];
char cur,root;
void dfs(char i){
if (i=='*'){
return;
}
cout<<i;
dfs(tree[i].left);
dfs(tree[i].right);
}
signed main()
{
cin>>n;
string in;
//便于在dfs时确定root的位置
cin>>root;
cin>>tree[root].left>>tree[root].right;
//正常遍历
for (int i=2;i<=n;i++){
cin>>cur;
cin>>tree[cur].left>>tree[cur].right;
}
dfs(root);
#define one 1
return 0;
}
2. 寻找根节点
方法二:
- 使用一个 n维3列的char数组来表示这棵树,[0]表示根节点的字符,[1] [2]分别表示左右孩子;我们便可以每次dfs的时候,直接寻找每一层的所有节点,如果这个节点的父亲为当前的root,则进入此节点的递归。
为什么遍历每一层寻找父亲节点等于root的节点就是当前需要dfs的节点?
- 因为是前序遍历,根在前,所有当前层的所有节点都是一个根,即从根开始dfs
//TODO: Write code here
int n,m;
const int N=1e5+10;
int nums[N];
char arr[50][3];
void dfs(char root){
if (root=='*'){
return;
}
cout<<root;
for (int i=1;i<=n;i++){
if (arr[i][0]==root){
dfs(arr[i][1]);
dfs(arr[i][2]);
}
}
}
signed main()
{
cin>>n;
string in;
for (int i=1;i<=n;i++){
cin>>arr[i][0]>>arr[i][1]>>arr[i][2];
}
dfs(arr[1][0]);
#define one 1
return 0;
}
* P3884 求二叉树深度,宽度,uv距离
深度如何求?
- dfs遍历树,然后记录一个cnt,更新最大深度 deep_max?可以,但是没有必要
宽度如何求?
- 使用bfs层序遍历,维护一个queue,然后记录每一层的queue的元素数量(left入,right入),queue存储的就是每一层的元素个数就是宽度,然后不断的循环,直到queue为0,则遍历完成,kuan_max每次取得 queue.size()与它的最大值? 没有必要
两个节点之间的距离?
- 最短路径,三种随便选择一个即可,我选择 floyd
如果我们知道了 这个最短距离不就知道了 宽度与深度了吗,那我们就没有必要写两个算法求了。
深度如何知道?
- 根节点 1 到任意一点的最短距离的长度的最大值,就是深度的最大值(两个节点之间dis=1)
宽度如何知道?
- 根节点到 两个点 i j 的最短距离都是 n,则这 i j 属于同一层,属于同一层的最短距离一定相同,记录每一层的最短距离相同的元素的数量即可。
//TODO: Write code here
int n,m;
const int N=1e2+10;
int nums[N],dp[N][N];
signed main()
{
cin>>n;
int a,b;
memset(dp,0x3f,sizeof(dp));
for (int i=1;i<=n-1;i++){
cin>>a>>b;
dp[a][b]=1; //父亲到孩子的距离是1
dp[b][a]=2; //孩子到父亲的距离是2
dp[a][a]=dp[b][b]=0;
}
for (int k=1;k<=n;k++){
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
}
}
}
int deep=0;
//深度
for (int i=1;i<=n;i++){
deep=max(deep,dp[1][i]);
}
//宽度
int kuan[N]{};
int kuan_dis=0;
for (int i=1;i<=n;i++){
++kuan[dp[1][i]];//每一层到根节点的距离都是一样的
kuan_dis=max(kuan_dis,kuan[dp[1][i]]);
}
int u,v;
cin>>u>>v;
cout<<deep+1<<endl;
cout<<kuan_dis<<endl;
//uv之间的距离
cout<<dp[u][v]<<endl;
#define one 1
return 0;
}