基环树

基环树

步骤一(找环)

只要找到环的边

只要找到一个环的两个端点,然后对于两个端点进行分类讨论,也就是把这条链断开再合上的情况:骑士

因为他只要找到一条会把原来的树形结构变成环的边。那么我们用并茶几(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;
}
posted @   黄小轩  阅读(140)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示