关于基环树

关于基数环

含义

啥事基数环呢?

简单来说就是树上在加一条边。

它形如一个环,环上每个点都有一棵子树的形式。

因此,对基环树的处理大部分就是对树处理和对环处理。

显然,难度在于后者。

求环

不难理解,复杂度 \(O(n)\)

struct edge {
  	int nxt,to,w;
}e[N<<1];
int tot, head[N];
int vis[N],cnt,ring[N],dist[N],inring[N],rt;
int getring(int x,int fa){
	if(vis[x]){
	    rt=x;
	    return 1;
	}
	vis[x]=1;
	int tmp;
	for(int i=head[x];i;i=e[i].nxt){
		int to=e[i].to;
	    if(to==fa) continue;
		tmp=getring(to,x)
	    if(tmp){
	      	if(tmp==1){
				ring[++cnt]=x;
				dist[cnt]=e[i].w;
				inring[x]=1;
				if(x!=rt) return 1;
	      	}
	      	return 2;
	    }
	}
	return 0;
}

接下来就是做题了...

P5022 [NOIP2018 提高组] 旅行

分析

注意到数据点并奀,且前 \(16\) 个测试点满足 \(m=n-1\),也就是一棵树,果断 DFS 拿 60pts 跑路

你别说我还真写了...

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define Elaina 0
inline int read(){
	int f=1,x=0;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
	for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
	return f*x;
}
const int N=5010;
const int inf=INT_MAX;

vector<int> vec[N];
int n,m;
int ans[N],cnt;
int vis[N];
void dfs(int x,int fa){
	if(vis[x]) return ;
	vis[x]=1;
	ans[++cnt]=x;
	for(auto i:vec[x]){
		if(i==fa) continue;
		dfs(i,x);
	}
}

signed main(){
	n=rd,m=rd;
	int u,v;
	for(int i=1;i<=m;i++){
		u=rd,v=rd;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	
	for(int i=1;i<=n;i++)
		sort(vec[i].begin(),vec[i].end());
		
	dfs(1,0);
	
	for(int i=1;i<=cnt;i++){
		printf("%lld ",ans[i]);
	}
	return Elaina;
}

咳咳...

后面几个点满足 \(m=n\) 也就是一颗基环树。

由于基环树就是在树上多加了一条边,所以我们暴力枚举要拆下的一条边即可。

还是比较水的...

Code

Elaina's Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define Elaina 0
inline int read(){
	int f=1,x=0;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
	for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
	return f*x;
}
const int N=1e5+100;

vector<int> vec[N];
int n,m,res[N],ans[N],tot,idx,vis[N];
int dfrm,dto;
struct EDGE{
	int from,to;
}e[N<<1];

void dfs2(int x,int fa){
	res[++tot]=x;
	for(auto i:vec[x]){
		if(i==fa||vis[i]||(x==dfrm&&i==dto)||(x==dto&&i==dfrm)) continue;
		vis[i]=1;
		dfs2(i,x);
	}
}

void dfs1(int x,int fa){
	ans[++tot]=x;
	for(auto i:vec[x]){
		if(i==fa||vis[i]) continue;
		vis[i]=1;
		dfs1(i,x);
	}
}

bool check(){
	for(int i=1;i<=n;i++){
		if(ans[i]>res[i]) return 1;
		else if(ans[i]<res[i]) return 0;
	}
	return 0;
}

signed main(){
	n=rd,m=rd;
	int x,y;
	for(int i=1;i<=m;i++){
		x=rd,y=rd;
		vec[x].push_back(y);
		vec[y].push_back(x);
		e[i]={x,y};
	}
	for(int i=1;i<=n;i++) sort(vec[i].begin(),vec[i].end());
	
	if(m==n){
		bool fg=1;
		for(int i=1;i<=n;i++){
			dfrm=e[i].from,dto=e[i].to;
			for(int j=1;j<=n;j++) vis[j]=0,res[j]=0;
			tot=0;
			vis[1]=1;
			dfs2(1,0);
			if(tot<n) continue;
			if(fg){
				fg=0;
				for(int i=1;i<=n;i++){
					ans[i]=res[i];
				}
			}
			if(check()){
				for(int i=1;i<=n;i++){
					ans[i]=res[i];
				}
			}
		}
	}else{
		vis[1]=1;
		dfs1(1,0);
	}
	for(int i=1;i<=n;i++){
		printf("%lld ",ans[i]);
	}
	return Elaina;
}
/*
6 6
1 2
1 3
1 4
1 5
1 6
2 3
*/

P5049 [NOIP2018 提高组] 旅行 加强版

分析

交上一份代码显然会T掉 不T还叫什么加强版啊喂

守丸一下可以发现,当我们在环上走的时候,只有其出边为环上的那个点编号最大,且比回溯后第一个走的点还大,这时候才回溯,其他时候就正常跑DFS。

代码实现上可以用一个 \(flag\) 来标记是否需要回溯,用 \(mx\) 记录当前节点中第一个比环上的出边那个节点还要大的节点,以便后面判断是否回溯时比较。

代码有猪食()

Code

Elaina's Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define Elaina 0
inline int read(){
	int f=1,x=0;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
	for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
	return f*x;
}
const int N=500010;
const int inf=0x7fffffff;

int n,m,vis[N],ans[N],cnt,f[N],ring[N],flag,mx,is_trackback,head[N],idx;
struct EDGE{
	int nxt,to;
}e[N<<1];

void add(int x,int y){
	e[++idx].to=y;
	e[idx].nxt=head[x];
	head[x]=idx;
}

struct Node{
	int frm,to;
}node[N<<1];

bool cmp(Node x,Node y){
	return x.to>y.to;
}

void dfs1(int x,int fa){
	if(vis[x]) return ;
	vis[x]=1;
	ans[++cnt]=x;
	for(int i=head[x];i;i=e[i].nxt){
		int to=e[i].to;
		if(to==fa) continue;
		dfs1(to,x);
	}
}

void GetRing(int x,int fa){//找出图中的环
	if(flag) return;
	if(!f[x]) f[x]=fa;
	else if(f[x]!=fa){
		while(x!=fa){
			ring[fa]=1;
			fa=f[fa];
		}
		ring[x]=1;
		flag=1;
		return;
	}
	for(int i=head[x];i;i=e[i].nxt){
		int to=e[i].to;
		if(to==fa) continue;
		GetRing(to,x);
	}
}

void dfs2(int x,int fa){
	if(vis[x]) return;
	vis[x]=1;
	ans[++cnt]=x;
	if(ring[x]){//如果在环上
		int flag=0;
		for(int i=head[x];i;i=e[i].nxt){
			if(is_trackback) break;//temp标记是否回溯过
			int to=e[i].to;
			if(ring[to]&&!vis[to]){
				i=e[i].nxt;
				while(vis[e[i].to]) i=e[i].nxt;//跳过已经被访问的节点
				if(i) mx=e[i].to;//i不为0即环上的出边不是最大的出边,mx记录第一个比环的出边大的那个点 
				else if(to>mx){//环上的出边是最大的出边且比我们回溯后第一次要走的节点还大 
					flag=1,is_trackback=1;//需要回溯
				}
				break;
			}
		}
		for(int i=head[x];i;i=e[i].nxt){
			int to=e[i].to;
			if(ring[to] && flag) continue;//flag=1 需要回溯,不再继续往下走
			dfs2(to,x);
		}
	}else{
		for(int i=head[x];i;i=e[i].nxt){
			int to=e[i].to;
			if(to==x) continue;
			dfs2(to,x);
		}
	}
}

signed main(){
	n=rd,m=rd;
	int u,v;
	for(int i=1;i<=m;i++){
		u=rd,v=rd;
		node[i]={u,v};
		node[i+m]={v,u};
	}
	sort(node+1,node+1+2*m,cmp);
	for(int i=1;i<=2*m;i++){
		add(node[i].frm,node[i].to);
	}
	if(m==n-1){
		dfs1(1,0);
	}else{
		GetRing(1,1);
		flag=0;
		mx=inf;
		dfs2(1,0);
	}
	for(int i=1;i<=cnt;i++){
		printf("%lld ",ans[i]);
	}
	return Elaina;
}

\[\color{red}{\LARGE 我永远喜欢满穗!} \]

posted @ 2024-07-23 06:35  Elaina_0  阅读(28)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end