基环树
基环树
步骤一(找环)
只要找到环的边
只要找到一个环的两个端点,然后对于两个端点进行分类讨论,也就是把这条链断开再合上的情况:骑士
因为他只要找到一条会把原来的树形结构变成环的边。那么我们用并茶几(O.o)去维护当前两个点的状态,然后如果已经在一个集合里面了,就不加边而是存起来,最后对这两个点分别做一次简单的树形dp。并且保证当前这个点不选!
int find(int x)
{
return x==p[x]?x:p[x]=find(p[x]);
}
for(int i=1;i<=n;i++)
{
int a,b;
cin>>a>>b;
int ra=find(a),rb=find(b);
if(ra==rb)// 如果已经在了 namo就直接把这条边存起来
{
ed.ps({a,b});
}
else
{
p[ra]=rb;
add(a,b);add(b,a);
}
}
要找到环上的每个点
我更喜欢那个类似割点的写法,感觉有种莫名的熟悉感:岛屿
先介绍一下思路:每次都把一个点都到栈里面,然后记录一下每个点的转移过来的点和他的转移过来的权值,然后用for循环去把这个环的所有的的点找出来,用了足足12个数组
int cir[N],ed[N],cnt;
int st[N],is[N],fa[N],fw[N],s2[N];
int sum[N],d[N],d1[N],d2[N],q[N];
int ans;
void dfs_c(int u,int form)
{
is[u]=st[u]=1;//一个点在不在栈里面
for(int i=h[u];~i;i=ne[i])
//小细节挺烦人的,我需要枚举一个点不能走回去的时候,不能看他的爹有没有走过,而是要准确要边的情况,因为如果 你用j==pr 那么只有两个点的时候你无法转移。
{
int j=e[i],y=w[i];
if(i==(form^1))continue;
fa[j]=u;fw[j]=y;// 记录他从那个转移过来同时记录他的边权
if(!st[j])dfs_c(j,i);//如果这个点没有走过,就先dfs
else if(is[j])
{
// cout<<j<<" "<<cnt<<endl;
cnt++;
ed[cnt]=ed[cnt-1];//当前的下标从上一个环的结尾开始。
int sm=y;//前缀和,相当于把u当成起点,然后维护一个环。
for(int k=u;k!=j;k=fa[k])//直接走了一圈到了j为止
{
cir[++ed[cnt]]=k;//注意是++ed[cnt]
sum[k]=sm;//
sm+=fw[k];//
}
cir[++ed[cnt]]=j;//最后把j加上
sum[j]=sm;//前缀合。
}
}
is[u]=0;//出栈
}
步骤二
环上讨论
断环
也就是第一种题型,把这个环断开的影响。那么我们就可以直接找到一个环的可以断开的地方,然后对这个两个端点进行分开讨论。
骑士(基环树上最大权独立集问题)
每个点有个不能共存的点,每个点有个权值,问你最大权值。首先这个问题明显是个基环树问题,因为他有n个点n条边,同时他每个点都有一条边。然后我们如果没有基环的话,就是一个类似于没有上司的舞会的板子树上dp,如果有了基环,我们考虑先把这个链不考虑(先把他的临界的那条边的两个端点记为u,v),如果u没有选,那么v随便选,相当于没有这条边,也就相当于断了环,如果u选了,相当于v一定不能选,那么就相当于我分别从u和v出发,对这个树进行一次树上dp,其中当前点一定不能选的max。
// ed是上文中的如果连了就会变成环的边
LL dfs(int u,int pr)
{
d[u][0]=0,d[u][1]=w[u];
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==pr)continue;
dfs(j,u);
d[u][1]+=d[j][0];
d[u][0]+=max(d[j][0],d[j][1]);
}
return d[u][0];
}
for(auto t:ed)
{
int x=t.x,y=t.y;
ans+=max(dfs(x,-1),dfs(y,-1));
}
复制环
岛屿(基环树直径问题)
首先先把这个问题分成两个部分,1.环上问题,2.树上问题。一个答案的最大值要么是颗树的最大值和最大值之和,要么就是环上两个点的距离+树上的直径。
第一个比较好讨论直接再树上dfs的过程中比较一下,第二个情况我们先把环上的点找出来,然后根据某环路运输的题目的操作,对环上的点化环成链,然后对于每个点都记录一个前缀合和当前点的 树上直径,然后维护一个d和s数组,d表示直径,s表示前缀合,维护一个 d[i]+s[i]+d[j]-s[j]这个柿子。同时维护一个单调队列,保证队列中的长度小于等于n,因为他要保证只能走一圈,然后队列中的保证d[i]-s[i]单调。
int dfs2(int u,int form)//这个form 。。。同理
{
d1[u]=0;//最大值
d2[u]=0;//次大值
for(int i=h[u];~i;i=ne[i])
{
int j=e[i],y=w[i];
if(st[j])continue;//这里是防止走到了环上
if(i==(form^1))continue;
int t=dfs2(j,i)+y;// 当前点的边权。
// 典型的求直径咯
if(t>d1[u])
{
d2[u]=d1[u];
d1[u]=t;
}
else if(t>d2[u])d2[u]=t;
}
ans=max(ans,d1[u]+d2[u]);//可能ans 出现在同一颗树内。
return d1[u];
}
memset(st,0,sizeof st);//再次用于 dfs2
for(int i=1;i<=ed[cnt];i++)st[cir[i]]=1;//防止走了环上的点
for(int i=1;i<=cnt;i++)//枚举环
{
1 int sz=0;
ans=0;
for(int j=ed[i-1]+1;j<=ed[i];j++)
//每个环的下标都是从上一个的结尾开始,所以只要记录一下每个环的结尾的下标
{
int k=cir[j];
// cout<<k<<endl;
d[sz]=dfs2(k,-1);//找出直径
// cout<<d[sz]<<endl;
s2[sz]=sum[k];//
// cout<<k<<" "<<s2[sz]<<" "<<d[sz]<<endl;
sz++;//放到一个新的数组方便拓展。
}
//拓展记得 后面半段的前缀合的特别处理,要加上一圈。
for(int j=0;j<sz;j++)s2[j+sz]=s2[sz-1]+s2[j],d[j+sz]=d[j];
int hh=0,tt=-1;
//下面就是朴实无华的单调队列。
for(int j=0;j<2*sz;j++)
{
if(hh<=tt&&j-q[hh]>=sz)hh++;
if(hh<=tt)ans=max(ans,s2[j]-s2[q[hh]]+d[j]+d[q[hh]]);
while(hh<=tt&&d[j]-s2[j]>=d[q[tt]]-s2[q[tt]])tt--;
q[++tt]=j;
}
// cout<<ans<<endl;
res+=ans;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】