基环树专题总结

基环树专题总结

基环树,顾名思义是一棵树中只含一个环,而有关基环树的题便大多与这个环有关。

例如原本树上\(dp\)因为在基环树上会有后效性,所以断环后再进行\(dp\)等等,下面便来总结下做基环树题的常见套路。

求环

所有关于基环树的题没有不需要求环的,下面是我求环的板子,因为这样的方法显然不会遍历到所有点,所以需要额外单独\(dfs\)一次来标记已经考虑过的节点。

ll dfs2(ll x,ll fa)
{
	ll i,j;
	if(book[x]){rt=x;return 1;}
	book[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		ll to=a[i].to;
		if(i==fa) continue;  //按边来判断! 毕竟两个点组成环也是可以的 
		ll pd=dfs2(to,i^1);
		if(pd)
		{
			if(pd==1)
			{
				sta[++top]=x;
			    color[x]=1;
			    if(x!=rt) return 1;
			}
			return 2;
		}
	}
	return 0;
}

基环树上DP

T1 「ZJOI2008」骑士

对于环上每个点对应的子树做一遍“没有上司的舞会”,然后将环变成链,规定第\(1\)个节点必选还是必不选做两遍\(DP\)即可。

#include<bits/stdc++.h>
#define ll long long
#define N 2100000
#define M 4400000 
using namespace std;
ll n,val[N],cnt=2,ans;
ll vis[N],book[N],head[N];
struct note{
	ll to,nxt;
}a[M];
void add(ll x,ll y)
{
	a[cnt].to=y;
	a[cnt].nxt=head[x];
	head[x]=cnt++;
}
void dfs1(ll x,ll fa)
{
	ll i,j;
	vis[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		ll to=a[i].to;
		if(vis[to]||to==fa) continue;
		dfs1(to,x);
	}
} 
ll sta[N],top,rt,color[N];
ll dfs2(ll x,ll fa)
{
	ll i,j;
	if(book[x]){rt=x;return 1;}
	book[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		ll to=a[i].to;
		if(i==fa) continue;  //按边来判断! 毕竟两个点组成环也是可以的 
		ll pd=dfs2(to,i^1);
		if(pd)
		{
			if(pd==1)
			{
				sta[++top]=x;
			    color[x]=1;
			    if(x!=rt) return 1;
			}
			return 2;
		}
	}
	return 0;
}
ll dp1[N][2],dp2[N][2];
void calc(ll x,ll fa)
{
	ll i,j;
	dp1[x][0]=0;dp1[x][1]=val[x];
	for(i=head[x];i;i=a[i].nxt)
	{
		ll to=a[i].to;
		if(to==fa||color[to]) continue;
		calc(to,x);
		dp1[x][1]+=dp1[to][0];
		dp1[x][0]+=max(dp1[to][1],dp1[to][0]);
	}
}
ll solve(ll pd)
{
	ll i,j;
	dp2[1][1]=(pd?dp1[sta[1]][1]:0);
	dp2[1][0]=dp1[sta[1]][0];
	for(i=2;i<=top;i++)
	{
		dp2[i][0]=dp1[sta[i]][0]+max(dp2[i-1][1],dp2[i-1][0]);
		dp2[i][1]=dp2[i-1][0]+dp1[sta[i]][1];
	}
	return pd?dp2[top][0]:max(dp2[top][1],dp2[top][0]);
}
int main()
{
	ll i,j,x;
	scanf("%lld",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%lld%lld",&val[i],&x);
		add(i,x),add(x,i);
	}
	ans=0;
	for(i=1;i<=n;i++)
	{
		if(vis[i]) continue;
		dfs1(i,0);top=0;
		dfs2(i,0);
		for(j=1;j<=top;j++) calc(sta[j],0);
		ans+=max(solve(0),solve(1));
	}
	printf("%lld\n",ans);
	return 0;
}

T2 创世纪

又是一道类似上司的舞会的题,同样也是断边后两遍\(dp\),也有两种写法,第一种先断,第二种后断。

#include<bits/stdc++.h>
#define ll long long
#define N 1500000
#define M 3000000
using namespace std;
int n,head[N],cnt=2,ans,book[N];
int arr[N],vis[N],c[N],rt,js,top;
struct note{
	int to,nxt,w;
}a[M];
void add(int x,int y,int z)
{
	a[cnt].to=y;
	a[cnt].w=z;
	a[cnt].nxt=head[x];
	head[x]=cnt++;
}
void predfs(int x,int fa)
{
	int i;
	book[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(to==fa||book[to]) continue;
		predfs(to,x);
	}
}
int dfs(int x,int fa)
{
	int i;
	if(vis[x]==1)
	{
		vis[x]=2;arr[++top]=x;c[x]=1;
		return 1;
	}
	vis[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(i==fa) continue;
		int now=dfs(to,i^1);
		if(now)
		{
			if(vis[x]!=2)
			{
				arr[++top]=x;c[x]=1;
				return 1;
			}
			else
			{
				return 0;
			}
		}
	}
	return 0;
}
int dp[N][2],used[N],B[N];//0为没选,1为选  的  最多数目。 
void Dp1(int x,int fa,int lim)
{
	int i,maxn=-1e9-7,pd=0,id,pd2=0;
	dp[x][0]=0;dp[x][1]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(i==fa||i==lim||(i==(lim^1))) continue;
		pd2=1;
		Dp1(to,i^1,lim);
	    dp[x][0]+=max(dp[to][1],dp[to][0]);
	    if(dp[to][0]>=dp[to][1])
		{
			dp[x][1]+=dp[to][0];
			pd=1;
		}
		else
		{
			dp[x][1]+=dp[to][1];
			if(dp[to][0]-dp[to][1]>maxn)
			{
				maxn=dp[to][0]-dp[to][1];
				id=to;
			}
		}
		//dp[x][1]=max(dp[x][0])
	}
	if(pd==0&&pd2==1)
	{
		dp[x][1]-=dp[id][1];
		dp[x][1]+=dp[id][0];
	}
	else if(pd2==0)
	{
		dp[x][1]=-1e9;
	}
}
void Dp2(int x,int fa,int lim)
{
	int i,maxn=-1e9,pd=0,id,pd2=0;
	dp[x][0]=0;dp[x][1]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(i==fa||i==lim||(i==(lim^1))) continue;
		pd2=1;
		Dp2(to,i^1,lim);
		dp[x][0]=dp[x][0]+max(dp[to][1],dp[to][0]);
		if(B[x])
		{
			dp[x][1]=dp[x][1]+max(dp[to][1],dp[to][0]);
			pd=1;
			continue;
		}
		if(dp[to][0]>=dp[to][1])
		{
			dp[x][1]+=dp[to][0];
			pd=1;
		}
		else
		{
			dp[x][1]+=dp[to][1];
			if(dp[to][0]-dp[to][1]>maxn)
			{
				maxn=dp[to][0]-dp[to][1];
				id=to;
			}
		}
	}
	if(pd==0&&pd2==1&&!B[x])
	{
		dp[x][1]-=dp[id][1];
		dp[x][1]+=dp[id][0];
	}
	else if(pd2==0&&!B[x])
	{
		dp[x][1]=-1e9;
	}
	else if(pd2==0&&B[x])
	{
		dp[x][1]=1;
	}
}
int solve(int x)
{
	int i,res=0;
	predfs(x,0);top=0;
//  printf("%d\n",top);
	dfs(x,0);
//	for(i=1;i<=top;i++) printf("%d ",arr[i]);
//	printf("\n");
	rt=arr[1];
	
	for(i=head[rt];i;i=a[i].nxt)
	{
		if(a[i].w==1)
		{
			js=i;
			break;
		}
    }
//    
	Dp1(rt,0,js);
//	printf("#:%d %d\n",dp[rt][1],dp[rt][0]);
	res=max(dp[rt][0],dp[rt][1]);
	B[a[js].to]=1;
	Dp2(rt,0,js);
	B[a[js].to]=0;
//	printf("#:%d %d\n",dp[rt][1],dp[rt][0]);
	res=max(res,dp[rt][0]);
//	printf("#:%d\n",res);
	return res;
}
int main()
{
	int i,x;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%d",&x);
		add(i,x,1);
		add(x,i,0);
	}
	for(i=1;i<=n;i++)
		if(!book[i])
			ans+=solve(i);
	printf("%d\n",ans);
	return 0;
}
#include <bits/stdc++.h>
#define N 1100000
using namespace std;
const int inf=1e9;
int n,fa[N];
int f[N],nxt[N],data[N],num,a[N],cnt,g[N][2],dp[N][2];
bool tag[N],pag[N];
inline void add(int x,int y){ nxt[++num]=f[x]; f[x]=num; data[num]=y; }
inline void get_line(int x,int y){
	while(x!=y){
		a[++cnt]=x; x=fa[x];
	}
	a[++cnt]=y;
}
inline void dfs(int x){
	tag[x]=1;
	int y,pp=0,res1=0,minn=inf;
	for(int i=f[x];i;i=nxt[i]){
		y=data[i]; if(pag[y]==1)continue;
		dfs(y);
		g[x][0]+=max(g[y][0],g[y][1]); 
		if(g[y][0]>g[y][1])pp=1;
		res1+=g[y][1]; minn=min(minn,g[y][1]-g[y][0]);  
	}
	if(pp==1)g[x][1]=g[x][0]+1;
	else if(minn!=inf)g[x][1]=res1-minn+1;
//	cout<<x<<" "<<g[x][0]<<" "<<g[x][1]<<endl;
}
int solve(int x){
	int y=x,xx=x,res=0; cnt=0;
	while(pag[x]==0){
		pag[x]=1; y=x; x=fa[x];
	}
	while(xx!=x)pag[xx]=0,xx=fa[xx];
	get_line(x,y);
	for(int i=1;i<=cnt;i++){ dfs(a[i]); }
	dp[1][1]=g[a[1]][1]; dp[1][0]=-inf;
	for(int i=2;i<=cnt;i++){
		dp[i][0]=max(dp[i-1][0],dp[i-1][1])+g[a[i]][0];
		dp[i][1]=max(dp[i-1][1]+g[a[i]][1],dp[i-1][0]+g[a[i]][0]+1);
	}
	res=max(dp[cnt][0]+g[a[1]][0]+1-g[a[1]][1],dp[cnt][1]);
	dp[1][1]=-inf; dp[1][0]=g[a[1]][0];
	for(int i=2;i<=cnt;i++){
		dp[i][0]=max(dp[i-1][0],dp[i-1][1])+g[a[i]][0];
		dp[i][1]=max(dp[i-1][1]+g[a[i]][1],dp[i-1][0]+g[a[i]][0]+1);
	}
	res=max(res,max(dp[cnt][1],dp[cnt][0]));
	return max(res,0);
}
int main(){
//	freopen("test.in","r",stdin);
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%d",&fa[i]);
		add(fa[i],i);
	}
	int anss=0;
	for(int i=1;i<=n;i++){
		if(tag[i]==0){
			anss+=solve(i);
		}
	}
	cout<<anss;
}

基环树直径

有这一类题需要 算基环树的直径,求法是先找到环上每个点子树内的直径取最大值,同时保存以环上节点为端点的链的最长值,然后便在环上求经过环的路径最大值,使用单调队列便可轻松解决,然后和之前的最大值取\(max\)后就是答案。

T1「IOI2008」Islands

求每棵基环树的直径之和。

#include<bits/stdc++.h>
#define ll long long
#define N 2100000
#define M 4200000
#define pb push_back
#define pf push_front
#define popb pop_back
#define popf pop_front
using namespace std;
ll n,head[N],cnt=2,ans;
struct note{
	ll to,nxt,w;
}a[M];
void add(ll x,ll y,ll z)
{
	a[cnt].to=y;
	a[cnt].w=z;
	a[cnt].nxt=head[x];
	head[x]=cnt++;
}
ll st,tot,vis[N],arr[N],s[N],color[N],book[N];

ll dfs(ll x,ll fa)
{
	ll i;
	if(vis[x]==1)
	{
		vis[x]=2; arr[++tot]=x; color[x]=1;
		return 1;
	}
	vis[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		ll to=a[i].to;
		if(i==fa) continue;
		ll now=dfs(to,i^1);
		if(now)
		{
			if(vis[x]!=2)
			{
				arr[++tot]=x; color[x]=1; s[tot]=s[tot-1]+a[i].w;
				return 1; 
		    }
		    else 
		    {
		    	s[st]=s[tot]+a[i].w;
		    	return 0;
		    }
		}
	} 
	return 0;
}

ll dp[N],len[N],b[N],res;
void Dp(ll x,ll fa)
{
	ll i;
	for(i=head[x];i;i=a[i].nxt)
	{
		ll to=a[i].to;
		if(to==fa||color[to]) continue;
		Dp(to,x);
		res=max(res,dp[x]+dp[to]+a[i].w);
		dp[x]=max(dp[x],dp[to]+a[i].w);
    }
}
void dfs0(ll x,ll fa)
{
	ll i;
	book[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		ll to=a[i].to;
	    if(to==fa||book[to]) continue;
	    dfs0(to,x);
	}
}
ll solve(ll x)
{
	st=tot+1;res=0;
	ll i;
	dfs0(x,0);
	dfs(x,0);
	ll num=tot-st+1;
	for(i=st;i<=tot;i++)
	{
		Dp(arr[i],0);
		len[i-st+1]=dp[arr[i]];
	}
	b[1]=0;b[num+1]=s[st];
	for(i=2;i<=num;i++) b[i]=s[st+i-1];s[st]=0;
	for(i=num+2;i<=2*num;i++) b[i]=b[i-1]+s[st+i-num-1]-s[st+i-num-2];
	for(i=num+1;i<=2*num;i++) len[i]=len[i-num];
	deque<ll> q;
	q.clear();
	for(i=1;i<=num*2;i++)
    {
    	while(q.size()&&q.front()<=i-num) q.popf();
    	if(q.size())
    	{
    		res=max(res,len[i]+len[q.front()]+b[i]-b[q.front()]);
    	}
    	while(q.size()&&len[q.back()]-b[q.back()]<=len[i]-b[i]) q.popb();
    	q.pb(i);
    } 
    return res;
}
int main()
{
	ll i,x,z;
	scanf("%lld",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%lld%lld",&x,&z);
		add(i,x,z);add(x,i,z);
	}
	for(i=1;i<=n;i++)
		if(!book[i])
			ans+=solve(i);
	printf("%lld\n",ans);
	return 0;
}

发现性质题

T1 「NOIP2018」旅行 加强版

非加强版中的做法是断掉环上的边,然后每次都贪心的跑一边。

加强版中暴力断边肯定行不通了,需要直接发现在环上断那条边最优,可以发现当我们需要断掉环上一条边时,回溯后到达的节点肯定比当前下一个结点更优,所以保存每个环上节点的回溯到达节点,每当将要进入下一个节点时比较一下大小来判断是否断这条边即可。

#include<bits/stdc++.h>
#define ll long long
#define N 300000
#define M 600000
using namespace std;
int n,m,head[N],cnt=2,vis[N];
int st,c[N];
struct note{
	int to,nxt;
}a[M];
void add(int x,int y)
{
	a[cnt].to=y;
	a[cnt].nxt=head[x];
	head[x]=cnt++;
}
void dfs(int x)
{
	int i;
	priority_queue<int> q;
	printf("%d ",x);vis[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(vis[to]) continue;
		q.push(-to);
	}
	while(!q.empty())
	{
		dfs(-q.top());
		q.pop();
	}
}
int dfs1(int x,int fa)
{
	int i;
	if(vis[x]==1)
	{
		vis[x]=2;st=x;c[x]=1;
		return 1;
	}
	vis[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(i==fa) continue;
		int now=dfs1(to,i^1);
		if(now)
		{
			if(vis[x]!=2)
			{
				c[x]=1;
				return 1;
			}
			else
			{
				return 0;
			}
		}
	}
	return 0;
}
int book,nxt[N];
void solve(int x)
{
	int i;
	printf("%d ",x);
	priority_queue<int> q;
	vis[x]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
	    if(vis[to]) continue;
	    q.push(-to);
	}
	if(!c[x]||(x!=st&&c[x]&&book==0))
	{
		while(!q.empty())
		{
			solve(-q.top());
			q.pop();
		}
	}
	else
	{
		if(x==st)
		{
			while(!q.empty())
			{
				int now=-q.top();
				q.pop();
				if(!c[now]) solve(now);
				if(c[now]&&book==0)
				{
					nxt[now]=-q.top();
					book=1;
					solve(now);
					continue;
				} 
				if(c[now]&&book==1)
				{
					book=0;
					if(!vis[now])
					{
						solve(now);
					}
				}
			}
			return; 
		}
		if(book==1)
		{
			while(!q.empty())
			{
				int now=-q.top();q.pop();
				if(!c[now]) solve(now);
				else
				{
					if(!q.empty()) nxt[now]=-q.top();
					else nxt[now]=nxt[x];
					if(now<nxt[now]) solve(now);
					else continue;
				}
			}
			return;
		}
	}
}
int main()
{
	int i,u,v;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	if(m==n-1){dfs(1);return 0;}
    dfs1(1,0);
    memset(vis,0,sizeof(vis));book=0;
    solve(1);
	return 0;
} 
posted @ 2020-11-05 09:17  yzxx_qwq  阅读(217)  评论(0编辑  收藏  举报