• 基环树专题总结


    基环树专题总结

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

    例如原本树上(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
    ",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
    ",top);
    	dfs(x,0);
    //	for(i=1;i<=top;i++) printf("%d ",arr[i]);
    //	printf("
    ");
    	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
    ",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
    ",dp[rt][1],dp[rt][0]);
    	res=max(res,dp[rt][0]);
    //	printf("#:%d
    ",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
    ",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
    ",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;
    } 
    
  • 相关阅读:
    第8章 降维
    第7章 集成学习和随机森林
    JS利用async、await处理少见的登录业务逻辑
    SQL SERVER 实现多行转多列
    Mysql函数----控制流函数介绍
    继承----静态代码快、构造方法、代码块、普通方法的执行顺序
    RBAC----基于角色的访问权限控制
    秋招-----思特沃克视频面试总结
    tomcat启动失败的三种方法
    索引之----mysql联合索引
  • 原文地址:https://www.cnblogs.com/yzxx/p/13929938.html
Copyright © 2020-2023  润新知