数据结构—树上问题整理
简单二叉树
树的知识
树的遍历问题 :
1.首先是
树的先序 中序 后序遍历 主要就是中序决定了这颗树长什么样,中序找根分为左右子树,然后再不断去查找 !!!这个点挺重要的。如果是知道前序和后序而不知道根的话那就会出问题!! 因为当子节点只有一个时有两种情况!!
#include <bits/stdc++.h>
using namespace std;
int main()
{
char a[1005];
char b[1005];
cin>>a>>b; //这个就是求有多少种主要就是想明白什么情况会有多种答案
int i,j;
int len1=strlen(a);
int len2=strlen(b);
int cnt=0;
for(i=0;i<=len1;i++)
{
for(j=1;j<=len2;j++)
if(a[i]==b[j]&&a[i+1]==b[j-1])
cnt++;
}
cout<<pow(2,cnt);
}
2.中序和前序求后序
这就是找根分割!!!
#include <bits/stdc++.h>
using namespace std;
string a,b;
void dfs(string a,string b)
{
if(a.empty()||b.empty()) return ;
char c=a[0]; 这个是第一个根!!
int k=b.find(c); 然后找根分子树
string b1=b.substr(0,k); 切割
string b2=b.substr(k+1); 切割
a.erase(a.begin());
string a1=a.substr(0,k);
string a2=a.substr(k);
dfs(a1,b1);左子树
dfs(a2,b2);右子树
cout<<c; 最后输出符合条件!!
}
int main()
{
cin>>a>>b;
dfs(b,a);
}
求子树和
用记忆化搜索去跑 整个树这样就能求解出来!!
int solve(int x)
{
int sum=a[x].v;
if(a[x].l) sum+=solve(a[x].l);
if(a[x].r) sum+=solve(a[x].r);
v[x]=sum;
return sum;
}
树上路径
就是去跑一遍每一个节点用一个from数组去记录,也是一个搜索然后倒序输出。从u到v直接倒序输出!!
dfs(u);//跑一整个树搜索完!!
for(i=v;i!=u;i=from[i])
{
ans[++len]=i;
}
ans[++len]=u;
for(i=len;i;i--)
printf("%d ",ans[i]);
树的直径
dfs(1);//先跑一遍 找到距离最远的!!
for(i=1;i<=n;i++)
{
if(v<dist[i])
idx=i,v=dist[i];
}
memset(dist,0,sizeof(dist));
memset(from,0,sizeof(from));
from[idx]=-1;
dfs(idx);//再跑一遍找到距离最远的这时求的距离便是直径!!
只有在直径上跑才是最远的情况!!!可以仔细想想最远一定是先到直径所在的边的与直径相交的点加上离直径最远的点上。
树的中心
性质:到其他点的最长路径最短 以树的中心为整棵树的根时,从该根到每个叶子节点的最长路径最短
// LUOGU_RID: 137428120
//常用头文件! //向上取整 ans+mod-1 / mod
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ull=unsigned long long;
#define endl '\n'
#define INF 0x3f3f3f3f //这个是ll的最大值 可直接赋值
#define lINF 0x3f3f3f3f3f3f3f //这个是long long 的最大值
ll T;
bool cmp(pair<int,int> &a,pair<int,int> &b)
{
if(a.first!=b.first) return a.first>b.first;
else return a.second<b.second;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
ll n;
cin>>n;
vector<vector<int>> e(n);
vector<int> from(n,-2);vector<pair<int,int>> dist1(n),dist2(n);
vector<bool> st(n,0);//判断是否在直径上
vector<bool> flag(n,0);
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
--x;--y;
e[x].push_back(y);
e[y].push_back(x);
}
for(int i=0;i<n;i++)
dist1[i].first=0,dist1[i].second=i, dist2[i].first=0,dist2[i].second=i;
auto dfs=[&](auto self,int x,vector<pair<int,int>>&a)->void{
for(auto y:e[x])
{
if(from[x]!=y)
{
from[y]=x;
a[y].first=a[x].first+1;
self(self,y,a);
}
}
};
from[0]=-1;
dfs(dfs,0,dist1);
pair<int,int> t1,t2;
for(int i=0;i<n;i++)
{
if(dist1[i].first>t1.first)
{
t1.first=dist1[i].first;
t1.second=dist1[i].second;//找最远边
}
}
for(int i=0;i<n;i++)
dist1[i].first=0,dist1[i].second=i, dist2[i].first=0,dist2[i].second=i;
fill(from.begin(),from.end(),-2);
from[t1.second]=-1;
//以直径其中的一个端点搜索过去 初步想法将直径上的
dfs(dfs,t1.second,dist1);
ll cnt=0;
for(int i=0;i<n;i++)
{
if(dist1[i].first>t2.first)
{
t2.first=dist1[i].first;
t2.second=dist1[i].second;//找最远边
}
}
for(int i=t2.second;i!=t1.second;i=from[i])
{
st[i]=true;//在直径上
}
st[t1.second]=true;
sort(dist1.begin(),dist1.end(),cmp);
vector<array<int,3>> ans;
fill(from.begin(),from.end(),-2);
from[t2.second]=-1;
dfs(dfs,t2.second,dist2);
sort(dist2.begin(),dist2.end(),cmp);
for(int i=0;i<n;i++)
{
if(st[i])
{
flag[i]=true;
}
}
vector<array<int,4>> dist(n);
// cout<<dist1[3].first<<' '<<dist2[3].second<<endl;
for(int i=0;i<n;i++)
{
if(dist[dist1[i].second][0]<dist1[i].first)
{
dist[dist1[i].second]={dist1[i].first,dist1[i].second,t1.second,dist1[i].second};
}
if(dist[dist2[i].second][0]<dist2[i].first)
{
dist[dist2[i].second]={dist2[i].first,dist2[i].second,t2.second,dist2[i].second};
}
}
sort(dist.begin(),dist.end(),[&](array<int,4>&a,array<int,4>&b){return a[0]>b[0]; });
// cout<<cnt<<endl;
for(int i=0;i<n;i++)
{
if(!st[dist[i][1]])
{
cnt+=dist[i][0];
ans.push_back({dist[i][1]+1,dist[i][2]+1,dist[i][3]+1});
}
}
for(int i=0;i<n;i++)
{
//cout<<i<<' '<<st[i]<<endl;
if(st[dist2[i].second]&&dist2[i].first)
{
// cout<<dist2[i].second<<' '<<' '<<dist2[i].first<<endl;;
cnt+=dist2[i].first;
ans.push_back({t2.second+1,dist2[i].second+1,dist2[i].second+1});
}
}
cout<<cnt<<endl;
for (auto [x,y,z]:ans)
{
cout<<x<<' '<<y<<' '<<z<<endl;
}
}
树的中心为树直径的中点!! 当点数为奇数时中点唯一当点数为偶数时点数不唯一
暂时没找到应用!!!
树的重心
计算以无根树每个点为根节点时的最大子树大小,这个值最小的点称为无根树的重心。
1.当一个节点被选择成为根节点,它底下每个子节点的子树的大小(子树包含的节点数目)最大值最小的那个节点,被称为树的重心:
注 : 一棵树可能有1个或两个重心
性质:
1.以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
2.树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。反过来,距离和最小的点一定是重心。
3.把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
4.在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
5.树至多有两个重心。如果树有两个重心,那么它们相邻。此时树一定有偶数个节点,且可以被划分为两个大小相等的分支,每个分支各自包含一个重心。
#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> edge[100001];
int s[100001],from[100001],dist[100001];
void solve(int x)
{
s[x]=1;
for(auto y:edge[x])
{
if(y!=from[x])//找到子节点
{
from[y]=x;
solve(y);//先去解决儿子子树!!
s[x]+=s[y];
}
}
} //这样来得出所有子树的大小!!
void dfs(int x)
{
for(auto y:edge[x])
{
if(y!=from[x])
{
from[y]=x;
dist[y]=dist[x]+1;//去找中心应该是!!
dfs(y);
}
} 得出所有点以某点为根时的距离
}
int main()
{
scanf("%d",&n);
int i;
for(i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);//建立无向边 无根树!!
}
from[1]=-1;
solve(1);//求以1为根时所有的子树的大小!!
int v=1<<30,idx=0;
for(i=1;i<=n;i++)//找出最大子树最小的节点
{
int f=0;
for(auto y:edge[i])
{
if(y!=from[i])//儿子
f=max(f,s[y]);
else f=max(n-s[i],f); //求最大子树!!该点所连出去的最大子树 //变向的父亲用所有的减去该子树的大小就是外面那颗子树!!
}
if(v>f)
{
v=f;
idx=i; // 找到那个重心!!!用性质去找重心
} //因为该条件是充分必要条件!!!
}
from[idx]=-1;//最好这里要重新初始化一遍!!!!但是不用也可!
dfs(idx);
long long ans=0;
for(i=1;i<=n;i++)
ans+=dist[i];
printf("%lld",ans);
}
// LUOGU_RID: 137325360
//常用头文件! //向上取整 ans+mod-1 / mod
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ull=unsigned long long;
ll mn=INT64_MIN;
ll mx=INT64_MAX;
#define endl '\n'
#define INF 0x3f3f3f3f //这个是int的最大值 可直接赋值
#define lINF 0x3f3f3f3f3f3f3f //这个是long long 的最大值
int T;
void solve()
{
}
int main()
{
ll n;
cin>>n>>T;
vector<vector<int>> e(n);
vector<int> from(n,-2);vector<int> weight(n);vector<int> s(n);
vector<int> mss(n);
for(int i=1;i<n;i++)
{
int x;
cin>>x;
--x;
e[x].push_back(i);
e[i].push_back(x);
}
auto dfs=[&](auto self,int x)->void
{
s[x]=1;mss[x]=x;
for(auto y:e[x])
{
if(from[x]!=y)
{
from[y]=x;
self(self,y);
s[x]+=s[y];
weight[x]=max(weight[x],s[y]);
}
}
for(auto y:e[x])
{
int t=mss[y];
if(from[x]!=y)
{
while(t!=x)
{
if(max(weight[t],s[x]-s[t])<=s[x]/2)
{
mss[x]=t;
break;
}else
t=from[t];
}
}
}
};
from[0]=-1;
dfs(dfs,0);
// for(auto y:mss)
// cout<<y<<' ';
while(T--)
{
int x;
cin>>x;
x--;
cout<<mss[x]+1<<endl;
}
}
典型题目!!! http://oj.daimayuan.top/course/7/problem/530
树上 LCA 非常非常典型!!!
直接来一个最终版本!!!
先简单介绍一些比较不适用的做法:1.最大公共后缀 找到两个节点同时找到根路径,从后往前找最深的那个点。
2.让两点先跳到同一点!!然后一起往上跳!!直到跳到同一点!
#include <bits/stdc++.h>
using namespace std;
int n,m;
vector<int> edge[100001];
int father[100001][20],dist[100001];
void solve(int x)
{
for(auto y:edge[x])
{
dist[y]=dist[x]+1;
solve(y);
}
}
int main()
{
scanf("%d",&n);
int i,j;
for(i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
father[y][0]=x;
}
for(j=1;j<20;j++)// 利用倍增求出所有点的父亲节点 19 次方差不多50多个W 个点!!
for(i=1;i<=n;i++)
{
if(father[i][j-1])
father[i][j]=father[father[i][j-1]][j-1];
}
solve(1);
scanf("%d",&m);// m 个 询 问
for(i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
if(dist[x]<dist[y]) swap(x,y);
int z=dist[x]-dist[y];
for(int j=0;j<20&&z;j++,z/=2)//用二进制不断来跳!! 应该是跳到同一高度!!
{
if(z&1) x=father[x][j];
}
if(x==y)
{
printf("%d\n",x);
continue;
}
for(j=19;j>=0;j--)
{
if(father[x][j]!=father[y][j])
x=father[x][j],y=father[y][j];
}
printf("%d\n",father[x][0]);// 7:4 +2 +1 8 : 4 + 2 +1 +1 这个挺牛的!!!
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)