模板:环套树

54 篇文章 0 订阅
46 篇文章 0 订阅

前言

环套树者,一个环套一棵树也

解析

定义:n个点,n条边的无向连通图
其实就是树多了一条边,连出了一个环

性质:如果对环套树进行dfs,多出的一条非树边一定是一条返祖边

考虑dfs的过程如果它不是返祖边,dfs的时候就会直接从这条边过去,该边就会成为树边了

找环

如何在环套树上找到非树边?
考虑dfs,如果出边指向已经被搜过的点,那么这条边(和它的反向边)就是非树边
注意!:这里dfs的时候不能传来到当前节点的节点fa,而是要传来到这个条的边,否则在n=2的时候会找不到非树边

代码

void find_circle(int x,int pre){
	vis[x]=1;
	for(int i=fi[x];~i;i=p[i].nxt){
		if(i==(pre^1)) continue;
		int to=p[i].to;
		if(vis[to]){
			e=i;
			u=x;v=to;continue;
		}
		find_circle(to,i);
	}
} 

练习

城市环路
骑士
两道很接近的较水的题
解决环套树后就变成没有上司的舞会了

环套树的直径

找出环上的所有点
首先考虑不经过环的路径对每个点跑一遍树形dp,求出每个点不包含环上的点的子树的直径
答案首先可以对这些直径取ma尝试作为答案
下面考虑经过环的路径
刚才dp时可以顺便求出连在每个环上点上的最长链len
那么经过环的最长路径就可以表示为:
l e n i + l e n j + d i s t ( i , j ) len_i+len_j+dist(i,j) leni+lenj+dist(i,j)
考虑求上面的最大值,可以破环成链,倍长后用单调队列优化解决

代码

(本题调来调去,代码有些屎山)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;
#define ll long long
#define I register int
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}
	return x*f;
} 
int n;
struct node{
	int to,nxt,v;
}p[N<<1];
int fi[N],cnt=-1;
void addline(int x,int y,int v){
	p[++cnt]=(node){y,fi[x],v};
	fi[x]=cnt;
}
ll ans;
int u,v,fa[N],fv[N];
bool vis[N];
int id[N],E=-1;
void find_circle(int x,int pre){
	//printf("x=%d fa=%d\n",x,fa[x]);
	vis[x]=1;
	for(int i=fi[x];~i;i=p[i].nxt){
		if(i==(pre^1)) continue;
		if(i==E||i==(E^1)) continue;
		int to=p[i].to;
		if(vis[to]){
			E=i;u=x;v=to;continue;
		}
		fa[to]=x;fv[to]=p[i].v;
		find_circle(to,i);
	}
}
ll dis[N],d[N];
void dfs(int x,int f){
	ll mx=0,sec=0;d[x]=0;
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f||id[to]){
			//printf("  fail:x=%d to=%d\n",x,to);
			continue;
		}
		dfs(to,x);
		ll now=dis[to]+p[i].v;
		//printf("  x=%d to=%d now=%lld\n",x,to,now);
		if(now>mx) swap(now,mx);
		if(now>sec) swap(now,sec);
		d[x]=max(d[x],d[to]);
	}
	//printf("x=%d mx=%lld sec=%lld\n",x,mx,sec);
	dis[x]=mx;d[x]=max(d[x],mx+sec);
}
int q[N],st,ed,len;
ll sum[N<<1];
ll val(int x,int now){
	now--;
//	printf("val:x=%d now=%d id=%d res=%lld\n",x,now,id[x],dis[x]+max(sum[now]-sum[id[x]-1],sum[len]-(sum[now])+sum[id[x]-1]));
	return dis[x]+max(sum[now]-sum[id[x]-1],sum[len]-(sum[now])+sum[id[x]-1]);
}
ll a[N<<1],dd[N<<1];
void solve(int x){
	find_circle(x,-2);
	int uu=u;len=1;id[uu]=1;
	while(uu!=v){
		len++;
		uu=fa[uu];id[uu]=len;
	}
	uu=u;ll res=0;
	dfs(u,0);res=max(res,d[u]);
	while(uu!=v){
		uu=fa[uu];dfs(uu,0);
		res=max(res,d[uu]);
	}
	for(uu=u;1;uu=fa[uu]){
		//printf("u=%d dis=%lld d=%lld\n",uu,dis[uu],d[uu]);
		if(uu==v) break;
	}
	for(uu=u;uu!=v;uu=fa[uu]){
		a[id[uu]]=fv[uu];dd[id[uu]]=dis[uu];
		sum[id[uu]]=sum[id[uu]-1]+fv[uu];
	}
	a[len]=p[E].v;sum[len]=sum[len-1]+p[E].v;dd[len]=dis[v];
	for(int i=len+1;i<=2*len;i++){
		a[i]=a[i-len];dd[i]=dd[i-len];
		sum[i]=sum[i-1]+a[i];
	}
	//for(int i=1;i<=2*len;i++) printf("i=%d a=%lld sum=%lld dd=%lld\n",i,a[i],sum[i],dd[i]);
	st=1,ed=1;q[1]=1;
	for(int i=2;i<=2*len;i++){//printf("i=%d len=%d",i,len);
		while(st<=ed&&i-q[st]>=len) st++;
		//printf("  i=%d st=%d res=%lld+%lld+%lld-%lld\n",i,q[st],dd[q[st]],dd[i],sum[i],sum[q[st]]);
		res=max(res,dd[q[st]]+dd[i]+sum[i-1]-sum[q[st]-1]);
		while(st<=ed&&dd[q[ed]]-sum[q[ed]-1]<=dd[i]-sum[i-1]) ed--;
		q[++ed]=i;
	}
	ans+=res;
//	printf("x=%d res=%lld\n\n",x,res);
}
int main(){
	memset(fi,-1,sizeof(fi));
	n=read();
	for(int i=1;i<=n;i++){
		int x=read(),y=read();
		addline(i,x,y);addline(x,i,y);
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			solve(i);
		}
	}
	printf("%lld",ans);
}
/*
5
2 1
3 3
1 2
2 5
2 4
*/

thanks for reading!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值