• 基环树浅谈


    前言:怎么感觉基环树好像不是NOIP的知识点?(之前都没学过耶!

    尽管如此,今天还是来学了学基环树。(开始复习!


    一.概念:

    其实基环树也没有多难,也就比普通的树多了一条边而已啦!

    基环树,又叫环套树,是一种由点和边组成的图,含有一个环,而环上的每一个点又是一棵树的树根,即整颗树的根不是一个点而是一个环,所以称之为基环树。

    基环树的处理主要分为两部分,大家应该也能想到,树的部分与环的部分(后者一般比较恶心),我们一般的操作就是断开环上的一条边,使其变成一棵树,然后跑DFS或DP。

    基环树也分有向图和无向图,有向图中又有两种:内向树和外向树。

    内向树指的是每个点只有一条出边的基环树

    外向树指的是每个点只有一条入边的基环树

    如下图就是一个内向树(外向树把所有边反一下

    二.解决方法

    1.找环:

    无向图:拓扑排序

    可以把度数为(1)的点放入一个队列,不断更新,最后度数还剩下(2)的点就在环中,代码如下:

    queue <int> q;
    inline void topo()
    {
    	for(;!q.empty();)q.pop();
    	for(re int i=1;i<=n;i++)
    		if(in[i]==1)q.push(i);
    	for(re int u;!q.empty();)
    	{
    		u=q.front(),q.pop();
    		for(re int i=hd[u],v;i;i=e[i].net)
    			{
    				v=e[i].v,in[v]--;
    				if(in[v]==1)
    					q.push(v);
    			}
    	}
    }
    

    有向图:DFS

    这个更简单,直接爆搜,码量也很小!

    void dfs(int u)
    {
    	vis[u]=1;
    	for(re int v,i=hd[u];i;i=e[i].net)
    	{
    		v=e[i].v;
    		if(vis[v])Sign=v;
    		else dfs(v);
    	}
    }
    

    (Sign) 就是环中的一个点。

    2.断环:

    我们处理基环树的方式一般就是断开一条环上的边,然后在处理,当然也可以枚举终点和起点,把环上的点放入一个数组并复制一遍,就可以拿一个长度为环上点的个数(n)的区间在上面枚举了。

    三.例题:

    1.P5022 旅行

    本题为(2018)年提高(Day2)的题,其中(40)分,也就是(n=m)的情况,考了基环树。

    因为(nle5000),可以(O(n^2))做,所以这题很适合断环,枚举环上的每一条边,打上标记不得走,就变成前(60\%)一样的题了。

    讲完,上代码:

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    #define re register
    using namespace std;
    int n,m,len[5005],a[5005][5005],lon,ans[5005],in[5005],b[5005],KBL;
    bool vis[5005],yor[5005],viss[5005][5005];
    void dfs(int u)
    {
    	if(lon==n||KBL==-1)return;
    	vis[u]=1,lon++;
    	ans[lon]=u;
    	if(ans[lon]<b[lon])KBL=1;
    	else if(ans[lon]>b[lon]&&KBL==0){KBL=-1;return ;}
    	for(re int i=1;i<=len[u];i++)
    		if(vis[a[u][i]]==0&&viss[u][i]==0)dfs(a[u][i]);
    }
    queue <int> q;
    inline void topo()
    {
    	for(;!q.empty();)q.pop();
    	for(re int i=1;i<=n;i++)
    		if(in[i]==1)q.push(i);
    	for(re int u;!q.empty();)
    	{
    		u=q.front(),q.pop();
    		for(re int i=1;i<=len[u];i++)
    			if(in[a[u][i]])
    			{
    				in[a[u][i]]--;
    				if(in[a[u][i]]==1)
    					q.push(a[u][i]);
    			}
    	}
    }
    int main()
    {
    	memset(b,63,sizeof(b));
    	scanf("%d%d",&n,&m);
    	for(re int i=1,x,y;i<=m;i++)
    	{
    		scanf("%d%d",&x,&y);
    		in[x]++,in[y]++;
    		a[x][++len[x]]=y;
    		a[y][++len[y]]=x;
    	}
    	for(re int i=1;i<=n;i++)sort(a[i]+1,a[i]+len[i]+1);
    	if(n==m+1)
    	{
    		dfs(1);
    		for(re int i=1;i<=lon;i++)
    			b[i]=ans[i];
    	}
    	else if(n==m)
    	{
    		memset(b,63,sizeof(b));
    		topo();
    		for(re int i=1;i<=n;i++)
    		{
    			if(in[i]<2)continue;
    			for(re int j=1;j<=len[i];j++)
    			{
    				if(in[a[i][j]]<2)continue;
    				viss[i][j]=1,lon=0,KBL=0;
    				memset(vis,0,sizeof(vis)); 
    				dfs(1);
    				viss[i][j]=0;
    				if(KBL==1)
    				for(re int k=1;k<=n;k++)
    					b[k]=ans[k];
    			}
    		}
    	}
    	for(re int i=1;i<=n;i++)printf("%d ",b[i]);
    	return 0;
    }
    

    这题是在本蒟蒻没学领接表的时候写的,所以存边的方式有点。。


    2.P2607 骑士

    分析一下题目,发现就是基环树森林版的没有上司的舞会,所以我们就可以枚举每一颗基环树,随便断开其中一条边,跑DP就行了,不过因为断了一条边,所以那一条边必须有一端不能取,所以各枚举两端强行不取的情况就完成了。

    剩下的就和没有上司的舞会一样了。。

    手起,码落:

    #include<bits/stdc++.h>
    #define max(x,y) ((x)>(y)?(x):(y))
    #define re register
    #define inf 1e18
    using namespace std;
    const int N=1000005;
    struct edge{int v,net;}e[N];
    int n,a[N],fa[N],rt,cnt,hd[N];
    bool vis[N];
    long long f[N][2],ans;
    inline void add(int u,int v){e[++cnt].v=v,e[cnt].net=hd[u],hd[u]=cnt;}
    void DP(int u)
    {
    	vis[u]=1,f[u][0]=0,f[u][1]=a[u];
    	for(re int i=hd[u],v;i;i=e[i].net)
    	{
    		v=e[i].v;
    		if(v==rt){f[v][1]=-inf;continue;}
    		DP(v);
    		f[u][1]+=f[v][0];
    		f[u][0]+=max(f[v][0],f[v][1]);
    	}
    }
    inline void work(int x)
    {
    	rt=x,vis[rt]=1;
    	for(;vis[fa[rt]]==0;rt=fa[rt],vis[rt]=1);
    	DP(rt);
    	re long long mid=f[rt][0];
    	rt=fa[rt];
    	DP(rt);
    	ans+=max(mid,f[rt][0]);
    }
    int main()
    {
    	scanf("%d",&n);
    	for(re int i=1;i<=n;i++)
    		scanf("%d%d",&a[i],&fa[i]),add(fa[i],i);
    	for(re int i=1;i<=n;i++)
    		if(vis[i]==0)work(i);
    	printf("%lld",ans);
    	return 0;
    }
    
  • 相关阅读:
    C++成员变量与函数内存分配
    Sqlite ContentProvider Loader 上下文 对话框
    好书好人生--读书的步骤
    小智慧40
    流媒体开发之-直播界面切换电视台频道
    HDU 4617Weapon(两条异面直线的距离)
    BON取代半岛电视,美国人要“换口味”了吗?
    【Todo】Lucene系统学习
    Zookeeper学习 & Paxos
    C++中的虚继承 & 重载隐藏覆盖的讨论
  • 原文地址:https://www.cnblogs.com/jkzcr01-QAQ/p/13575211.html
Copyright © 2020-2023  润新知