树
1.洛谷P1030求先序排列
首先一棵树的排列有前序,中序,后序,分别是根左右,左根右和左右根。那么易知,后序的最后一个是根,在中序中找到根,他的左右就是左子树和右子树,对于子树的寻找根,只需要递归就可以了。一开始看的题解的代码实现,在中序中找到根是用的string.find(root),这个函数返回的是string中root的位置,递归实现找左右子根的函数时string.substr,这个函数输入两个参数,第一个是字符串的位置,第二个是长度,这个函数返回一个子字符串,是从原来的字符串的位置开始长度的子子字符串
2.2026Spring天梯赛1第九题
用的并查集的数据结构,并查集需要两个函数,一个用来查找根节点,一个用来把一个节点的根节点设置成另一个节点的根节点。而在查找根节点的函数中还有一个优化方法:路径压缩,这个方法会破坏树的结构,让每个子节点的上一节节点都变成自己的根节点,从而让整棵树变得扁平。因为这道题只关注一个节点的根节点,所以可以如此优化,好处就是以后查询的时候只需要O(1)的复杂度就可以完成根节点的查询。解决完这个问题之后有一个我最开始并不知道的问题:那就是如何查找一共有多少个圈子也就是看多少个区域联通。后来我发现如果有某个节点的根节点就是他自己那么可以说明我们找到了一个根,那么有多少个根就有多少个联通的区域。这道题最开始我是用的图的结构来写的,但是我发现啊这样其实并没有并查集更简单。
3.病毒溯源
首先这道题需要我们建一棵树,而根节点是唯一的,然后求出这棵树的最大深度和最小的最长序列。我们一个一个来解决,一棵树的建法和图有点不同,图可以用一个二维数组来表示,里面全是0或者INF,如果联通就用1或者距离但是如果里面的点位很稀疏的话会导致空间的利用不够高,而且查询O(n^2),很可能会超时。那么树的建立其实也是用二维数组,但是这个数组是只有一个维度是确定的,就是节点的个数,而每个节点的根直接用push_back推进去,这样可以节省时空间来记录。建完数之后我们也是用dfs的思想来查询最大的层数,首先肯定是从唯一的根节点开始dfs,而找到根节点的方法是建立一个父节点的数组,初始化为每个人都是自己的父亲,在建树的时候就把子节点的父亲记录为这个父节点。这样对这个数组一个遍历找到父节点任然是自己的节点,那么这个就是根节点。记录深度的dfs在传入参数的时候要传入现在的深度,在开始的深度是1,每一次下降深度都把深度+1。从根节点开始的话找到根节点的子节点,再把这个子节点作为下一次dfs的根节点。对于最大的深度我们需要时刻对他取当前最大值,因为我们会进行节点总个数的次数的dfs,所有的节点都遍历过之后我们就能得到最大的深度。最后剩下如何得到最小的最长序列,首先我们需要一个最终的序列和一个临时的序列,对于这个临时的来说我们每一次走到一个节点都可以把这个节点推进去,当dfs的深度来到最大的深度之后,我们已经得到了一个路径,这个时候我们与目前存在最佳路径的路径对比,选取最佳的。然后这个函数有个至关重要的操作,就是我们在遍历完一个节点的所有子节点之后需要将这个节点从现在的临时序列中删除,继续去找这个节点的同层级的节点的所有路径。我们将所有节点都遍历完之后也意味这所有路径的遍历完全,这个时候维护的最佳序列就是我们需要的答案。最后一个就是这道题的一个语法的知识,我的临时序列和最佳序列使用vector来建立的,而vector可以直接使用><符号来比较他们的字典序。
4.仪式网络
链接:
https://hydro.ac/d/CUIT_DSA/p/885?tid=69be7e9787636554247dc0a0
思路:
这道题因为我们需要检查树是否成环,所以考虑使用并查集,由于我们只需要知道节点的根节点就可以知道两个节点是否相连(节点的根节点开始初始化为自己),所以为了减少递归深度需要路径压缩。解决完数据结构的问题来思考思路。我们发现有n个节点n个边,所以一定只有一个环,那么我们只需要遍历环上的每个序号,维护最大值就可以了;但是优化的思路是由于我们的建边是按照编号顺序来建的,要满足只有一个环,我们只需要找到第一个使树成环的边的编号就可以找到最大的编号。因为假设第i个边让树成环,那么第i条边之后的边一定不会成环,那么这个环上所有的边的编号一定是i最大
代码:
代码语法内容已记录到周报:https://www.cnblogs.com/wtpavssite/p/19756866周一查看
点击查看代码
#include<bits/stdc++.h>
using namespace std;
class Union{
public:
vector<int>father;
Union(int n){
father.resize(n+1);
iota(father.begin(),father.end(),0);
}
int find(int x){
if(father[x]!=x){
father[x]=find(father[x]);
}
return father[x];
}
bool unite(int x,int y){
int rtx=find(x);
int rty=find(y);
if(rtx==rty){
return false;
}
father[rtx]=father[rty];
return true;
}
};
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0);
int n;
cin>>n;
Union ut(n);
int ans=-1;
for(int i=1;i<=n;i++){
int u,v;
cin>>u>>v;
if(ans==-1){
if(!ut.unite(u,v)){
ans=i;
}
}
}
cout<<ans<<endl;
return 0;
}
5.小红的树上博弈
来源:http:https://ac.nowcoder.com/acm/contest/130107/E//
开始的思路:
一开始我想的是计算当前所在的节点的子节点的子节点数量,毫无疑问如果当前节点的子节点的子节点数量里面有一个是0,由于小红总是先手那么小红就能到叶子节点,我觉得这样判定小红胜利一定是正确的,然后除开这种情况的时候,小红总是倾向与选择子节点数量最多的子节点,如果某个子节点的子节点数量只有1,那么小红一定不会选择这个节点进入,因为进去一定会被阻止,所以在一定程度上只要能找到一条能获胜的道路,小红就能必胜。
错误的原因:
显然错误的原因是每次都选择子节点的数量最多的子节点并不是最佳的策略,如果两个节点,一个节点里面100个子节点但都是必败,另一个节点只有一个子节点但是是必胜,那么我的策略会选择节点数量最多的那条路。
正确的思路:
不是比较谁的儿子多,而是比较谁的儿子更好,我们假设现在不在根节点也不再叶子节点,现在所在的节点的子节点里面只有一个节点是必胜的,那么小紫一定不会让我们进入那个节点,所以这种中间的普通节点一定需要子节点里面有两个及以上的必胜的节点,我们现在的节点才是必胜的,那么如果我们在根节点,由于先手的原因,我们只需要子节点里面有一个是必胜的节点,我们就一定能进去,在叶子节点就一定是必胜。所以我们从底到根来计算每个节点的必胜态就可以得到目前的过根节点的状态。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
vector<vector<int>>g;
void solve(){
int n;
cin>>n;
g.assign(n+1,vector<int>());
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int>f(n+1,0);
auto dfs=[&](auto&& self,int son,int fa)->void{
int cnt=0;
bool ok=1;
for(int u:g[son]){
if(u==fa)continue;
ok=0;
self(self,u,son);
cnt+=f[u];
}
if(ok)
f[son]=1;
else if(son==1)
f[son]=cnt>=1;
else
f[son]=cnt>=2;
};
dfs(dfs,1,0);
cout<<(f[1]?"red":"purple")<<endl;
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
}
浙公网安备 33010602011771号