基环树和笛卡尔树
基环树
就是比平常的树多一条边,有 条边,也就有一个环在里面。
基本思想就是断环,跑树形 ,或者用拓扑排序判环去跑环形 。
树的直径
今天才了解到的,用两遍 跑。
首先第一遍找到离根节点最远的节点 ,
然后再从 找到离它最远的节点 ,那么树链 就是树的直径。
在后面很有用。
#3437. [ZJOI2008]骑士
类似于树形 dp 的思路(没有上司的舞会)。
考虑状态转移方程:
其中 表示 的子节点。
然后基环树的思路是先找环,找到环的根之后跑树形 dp。
void dfs(int u) { vis[u]=1; dp[u][0]=0,dp[u][1]=a[u]; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v!=root) { dfs(v); dp[u][0]+=max(dp[v][0],dp[v][1]); dp[u][1]+=dp[v][0]; } else dp[v][1]=-1145141919; } } void find(int x) { vis[x]=1; root=x; while(!vis[fa[root]]) { root=fa[root]; vis[root]=1; } dfs(root); int tmp=max(dp[root][0],dp[root][1]); vis[root]=1; root=fa[root]; dfs(root); ans+=max(tmp,max(dp[root][1],dp[root][0])); }
#P1203. 「NOIP2018」旅行
P3533 [POI2012] RAN-Rendezvous
妈呀,打了一下午成小孩了。
调了一万次甚至动用科技,最后发现树剖炸了、
题目给出一个基环内向树森林,我们首先预处理出每个点所属环上的根节点 是谁,再对分别对每棵树进行树剖。
然后通过 计算每个环的编号 和长度 。
考虑两个点的状态进行分类讨论:
- 两点位于不同的基环树中
通过找环的时候给每个环进行编号,查询两点在环上根节点所属环是否一样进行判断;
- 两点位于同一基环树的同一子树中
意思是两点的 相同,我们可以直接查询 作为答案;
- 两点位于同一基环树不同子树中
比较两个 作为相遇点,哪个更符合要求即可。
#include<bits/stdc++.h> using namespace std; inline int read() { register int s=0,w=1; register char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') { s=(s<<1)+(s<<3)+(c^48);c=getchar(); }return s; } int n,m; const int N=5e5+1; vector<int>e[N],uu[N]; int indeg[N]; int q[N*2],tt; int cir[N]; struct HPD{ int siz[N],dep[N],son[N],fa[N]; int id[N],tim=0,top[N]; void dfs1(int u,int fat) { siz[u]=1,fa[u]=fat; if(!indeg[u]) dep[u]=dep[fat]+1; cir[u]=cir[fat]; for(int v:uu[u]) { if(v==fat||indeg[v]) continue; dfs1(v,u); siz[u]+=siz[v]; if(siz[v]>siz[son[u]]) son[u]=v; } } void dfs2(int u,int t) { top[u]=t; if(son[u]) dfs2(son[u],t); for(int v:uu[u]) { if(v==fa[u]||indeg[v]||v==son[u]) continue; dfs2(v,v); } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>=dep[top[v]]) u=fa[top[u]]; else v=fa[top[v]]; } return dep[u]<dep[v]?u:v; } }hpd; bool vis[N]; int len[N],pos[N]; int id[N],cur; void dfs(int u,int le) { len[u]=le,vis[u]=1,pos[u]=le; for(int v:e[u]) { if(vis[v]||!indeg[v]) continue; id[v]=id[u]; dfs(v,le+1); len[u]=len[v]; } } inline bool cmp(int x1,int y,int x2,int y2) { if(max(x1,y)!=max(x2,y2)) return max(x1,y)<max(x2,y2); if(min(x1,y)!=min(x2,y2)) return min(x1,y)<min(x2,y2); return x1>=y; } int main() { n=read(),m=read(); register int v; for(int i=1;i<=n;i++) { v=read(); e[i].push_back(v); indeg[v]++; uu[v].push_back(i); } for(int i=1;i<=n;i++) { if(!indeg[i]) q[++tt]=i; } register int now; while(tt!=0) { now=q[tt];tt--; v=e[now][0]; indeg[v]--; if(!indeg[v]) q[++tt]=v; } for(int i=1;i<=n;i++) { if(indeg[i]) { cir[i]=i; hpd.dfs1(i,i); hpd.dfs2(i,i); if(!vis[i]) id[i]=++cur,dfs(i,1); } } register int a,b; while(m--) { a=read(),b=read(); if(id[cir[a]]!=id[cir[b]]) { printf("-1 -1\n");continue; } // cout<<cir[a]<<" "<<cir[b]<<endl; if(cir[a]==cir[b]) { int lc=hpd.lca(a,b); // cout<<lc<<endl; printf("%d %d\n",hpd.dep[a]-hpd.dep[lc],hpd.dep[b]-hpd.dep[lc]); } else { int cira=pos[cir[a]],cirb=pos[cir[b]]; int mod=len[cir[a]]; // cout<<cira<<" "<<cirb<<endl; int t1=hpd.dep[a]+(cirb-cira+mod)%mod; int t2=hpd.dep[b]+(cira-cirb+mod)%mod; if(cmp(hpd.dep[a],t2,t1,hpd.dep[b])) printf("%d %d\n",hpd.dep[a],t2); else printf("%d %d\n",t1,hpd.dep[b]); } } }
#3441. 创世纪
奇妙贪心?!
- 考虑叶子节点:
它本身不会被选,但是它的父亲要被选。
所以直接打上 然后跳到父亲的父亲。
如果一个基环树的某个环上节点挂了一棵子树,那么这棵树必然会被本次操作全部分解。
- 考虑环:
上述操作结束之后必然只剩下环(或者不剩)
剩下环的条件是这个基环树本身就是个环(因为这是个基环树森林)
环的贡献是环长除以二 。
然后就结束了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!