基环树学习笔记

news2024/11/26 8:53:42

0.前言

只因环树学习笔只因。

如有错误欢迎指出。

1.基本概念

这名字读起来感觉有点矛盾,怎么可能树上面有一个环呢?

我们把它放到百度翻译里面看看。

顾名思义,基环树不是一棵树,而是一颗假树,他的定义是:如果一张无向连通图包含恰好一个环,则称它是一棵基环树。

比较容易发现,我们也可以把环上的所有边删掉,那么我们就会得到一个森林,再将每个树的答案贡献与环的贡献合并在一起,从而来解决我们基环树的问题。

或者,如果在环上任意拆除一条边,我们就能得到一棵树,也就使得求解树问题的一些思想得以用在它的身上,亦可以成为解决此类问题的关键。

最后,还是把两个基本但个人感觉并不是特别重要的概念粘在下面:

  • 如果一张有向弱连通图每个点的入度都为 1 1 1,则称它是一棵 基环外向树。

  • 如果一张有向弱连通图每个点的出度都为 1 1 1,则称它是一棵 基环内向树。

2.基环树中如何求环

刚才已经提出了解决基环树问题的两种思路,那么接下来我们面临的问题就是如何判环?这里提供两种思路(本人采用的是后面的一种)

(1)拓扑排序

显然,在排序完之后,如果入度仍然不为0,那么他就一定是环上点。

如果要求具体的顺序的话,可以考虑在环中随便找一个点,直接搜索就可以了。

void topsort()
{
    int l=0,r=0;
    for(int i=1;i<=n;i++) 
      if(in[i]==1) q[++r]=i;
    while(l<r)
    {
        int now=q[++l];
        for (int i=ls[now];i;i=a[i].next)
        {
            int y=a[i].to;
            if(in[y]>1)
            {
                in[y]--;
                if(in[y]==1) q[++r]=y;
            }
        }
    }
}

(2) 搜索

如果当前遇到的点先前搜索过,那么他就肯定在环上,然后可以用一个栈思想,一直弹回去就可以了。

int get_ring(int x,int last)//vis即在不在环上
{
	if(isring[x]==1)
	{
		vis[x]=1,isring[x]=2,ring.push_back(x);
		return 1;
	}
	isring[x]=1;
	for(int i=fst[x];i;i=arr[i].nxt)
	{
		int j=arr[i].tar;
		if(i==((last-1)^1)+1) continue;
		if(get_ring(j,i))
		{
			if(isring[x]!=2)
			{
				vis[x]=1,isring[x]=2;
				ring.push_back(x);
				return 1;
			}
			return 0;
		}
	}
	return 0;
}

3.题目中的应用

在题目中就是上述的两种思想的具体应用。

T1 P4381 [IOI2008] Island

题意即给你一颗基环树(不保证联通),让你求这棵树的最大直径。

本体采用上述的第一种思想。

我们将答案分为两种:

  • 这条直径不在环上,而是在环上点的一颗子树上,那么这个可以直接用传统的求最长直径的方法。
  • 对于另外一种情况,我们可以考虑将其分成三段,一段在一颗子树上,一段在环上,最后一段在另外一颗子树上。形式化的,定义 d x d_x dx 表示 x x x 不经过环上的点能够到达的最远的距离,在环上取两个点 u , v u,v u,v,求 m a x ( d u + d v + d i s u , v ) max(d_u+d_v+dis_{u,v}) max(du+dv+disu,v)

可以发现对于两种情况,我们都需要先预处理出 d d d,然后对于情况二,我们用单调队列来维护即可。

#include<bits/stdc++.h>
using namespace std;
#define maxe 1000005
#define maxn 1000005
#define int long long
struct node
{
	int tar,nxt,num;
}arr[maxe*2];
int graphe_cnt,fst[maxn];
void adds(int x,int y,int z) 
{
	arr[++graphe_cnt].tar=y,arr[graphe_cnt].nxt=fst[x],fst[x]=graphe_cnt,arr[graphe_cnt].num=z;
}
int n;
int isring[maxn];
bool vis[maxn],has,is[maxn];
vector<int> ring;
int sum[maxn*2];
void init()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;++i)
	{
		int x,y;
		scanf("%lld%lld",&x,&y);
		adds(i,x,y);
		adds(x,i,y);
	} 
}
int get_ring(int x,int last)//判环
{
	if(isring[x]==1)
	{
		is[x]=1;
		vis[x]=1,isring[x]=2,ring.push_back(x);
		return 1;
	}
	isring[x]=1;
	for(int i=fst[x];i;i=arr[i].nxt)
	{
		int j=arr[i].tar;
		if(i==((last-1)^1)+1) continue;
		if(get_ring(j,i))
		{
			if(isring[x]!=2)
			{
				vis[x]=1,is[x]=1,isring[x]=2;
				ring.push_back(x);
				sum[ring.size()-1]=sum[ring.size()-2]+arr[i].num;
				return 1;
			}
			else sum[0]=arr[i].num;
			return 0;
		}
	}
	return 0;
}
int d[maxn],nowans,ans;
void dfs(int x,int last)//预处理d
{
	vis[x]=true;
	for(int i=fst[x];i;i=arr[i].nxt)
	{
		int j=arr[i].tar,k=arr[i].num;
		if(i==((last-1)^1)+1) continue;
		if(vis[j]) continue;
		dfs(j,i);
		nowans=max(nowans,d[x]+d[j]+k);
		d[x]=max(d[x],d[j]+k);
	}
}
void get_d()
{
	for(int i=0;i<ring.size();++i) dfs(ring[i],0);
	int num=ring.size(); 
	for(int i=0;i<num;++i)
	{
		ring.push_back(ring[i]);
		if(i>1)
		sum[i+num]=sum[i+num-1]+sum[i]-sum[i-1];
		else
		sum[i+num]=sum[i+num-1]+sum[i];
	}
}
void get_ans()//单调队列
{
//	cout<<nowans<<endl;
	deque<int> p;
	p.push_front(0);
	for(int i=1;i<ring.size();++i)
	{
		while(!p.empty()&&i-p.front()>=ring.size()/2) p.pop_front();
		if(p.empty())
		{
			p.push_back(i);
			continue;
		}
		int j=p.front();
//		cout<<sum[i]-sum[j]+d[ring[j]]+d[ring[i]]<<endl;
		nowans=max(sum[i]-sum[j]+d[ring[j]]+d[ring[i]],nowans);
		while(!p.empty()&&d[ring[p.back()]]+sum[ring.size()-1]-sum[p.back()]<=d[ring[i]]+sum[ring.size()-1]-sum[i]) p.pop_back();
		p.push_back(i);
	}
	ans+=nowans;
}
void print_ans()
{
	printf("%lld\n",ans);
}
void clear()
{
	has=0,nowans=0;
	ring.clear();
	sum[0]=sum[1]=0;
}
signed main()
{
	init();
	for(int i=1;i<=n;++i)
	{
		if(vis[i]) continue;
//		cout<<i<<" yeah!"<<endl;
		clear();
		get_ring(i,0);
		get_d();
		get_ans();
	}
	print_ans();
	return 0;
}

T2 Rendezvous

这题同样采用上述的第一种思想。

可以发现,一个基环树除开环以后,会分成多个不同的子树森林。

我们将要求解的两个 a , b a,b a,b 分为以下几种情况进行求解:

  • a , b a,b a,b 两者不连通,那么无解,打一个并查集判断即可。
  • a , b a,b a,b 在一颗子树上,那么终点就是 l c a ( a , b ) lca(a,b) lca(a,b),所以答案就是 ( d e p a − ( d e p l c a ( a , b ) , d e p b − d e p l c a ( a , b ) ) (dep_a-(dep_{lca(a,b)},dep_b-dep_{lca(a,b)}) (depa(deplca(a,b),depbdeplca(a,b))。( d e p dep dep 表示深度,且以上操作可以通过、树上倍增进行维护。)
  • 那么显然,剩下的一种情况保证 a , b a,b a,b 所在子树的根节点必然在同一个环上。可以设这两个根节点分别为 p , q p,q p,q。显然,我们可以预处理 p , q p,q p,q 两个点的距离 d i s p , q , d i s q , p dis_{p,q},dis_{q,p} disp,q,disq,p。那么显然,我们就会得到两种答案,一种是从 p p p 走到 q q q,一种是从 q q q 走到 p p p,若是从 p p p q q q,那么答案为 ( d e p a + d i s p , q , d e p b ) (dep_a+dis_{p,q},dep_b) (depa+disp,q,depb),反之同理。接着将这些答案通过优先级判断来输出即可。
#include<bits/stdc++.h>
using namespace std;
#define maxe 500005
#define maxn 500005
struct node
{
	int tar,nxt;
}arr[maxe*2];
int graphe_cnt,fst[maxn];
void adds(int x,int y) 
{
	arr[++graphe_cnt].tar=y,arr[graphe_cnt].nxt=fst[x],fst[x]=graphe_cnt;
}
vector<int> g[maxn];
int n,m;
int isring[maxn],vis[maxn];
bool used[maxn],has;
int times=0,now;
vector<int> ring[maxn];
int fa[maxn],in[maxn];
int findroot(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=findroot(fa[x]);
}
void unionn(int x,int y)
{
	int p=findroot(x),q=findroot(y);
	if(p!=q) fa[p]=q;
}
void init()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=n;++i)
	{
		int x;
		scanf("%d",&x);
//		if(x==i) continue;
		g[x].push_back(i);
		adds(i,x);
		unionn(i,x);
		in[x]++;
	} 
}
int get_ring(int x,int last)
{
	if(isring[x]==1)
	{
		vis[x]=times,isring[x]=2,ring[times].push_back(x);
		return 1;
	}
	isring[x]=1;
	for(int i=fst[x];i;i=arr[i].nxt)
	{
		int j=arr[i].tar;
		if(get_ring(j,i))
		{
			if(isring[x]!=2)
			{
				vis[x]=times,isring[x]=2;
				ring[times].push_back(x);
				return 1;
			}
			return 0;
		}
	}
	return 0;
}
int dp[maxn][21],dep[maxn],num[maxn],belong[maxn];
void dfs(int x,int last)//预处理lca
{
	belong[x]=now;
	dp[x][0]=last,dep[x]=dep[last]+1;
	for(int i=1;i<=20;++i) dp[x][i]=dp[dp[x][i-1]][i-1];
	for(int i=0;i<g[x].size();++i)
	{
		int j=g[x][i];
		if(vis[j]==vis[now]) continue;
		dfs(j,x);
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	int p=dep[x],q=dep[y];
	for(int i=20;i>=0;--i) if(p-(1<<i)>=q) p-=(1<<i),x=dp[x][i];
	if(x==y) return x;
	for(int i=20;i>=0;--i) if(dp[x][i]!=dp[y][i]) x=dp[x][i],y=dp[y][i];
	return dp[x][0];
}
void prepare()
{
	for(int i=1;i<=n;++i)
	{
		int p=findroot(i);
		if(used[p]) continue;
		used[p]=1;
		++times,has=false;
		get_ring(i,0);
	}
	//处理环上两点距离
	for(int i=1;i<=times;++i)
	for(int j=0;j<ring[i].size();++j)
	now=ring[i][j],dfs(ring[i][j],0);
	for(int i=1;i<=times;++i)
	{
		for(int j=0;j<ring[i].size();++j)
		num[ring[i][j]]=j;
	}
}
pair<int,int> get_ans(int x,int y)//求解,写的有点小丑。
{
	int Lca=lca(x,y),p=belong[x],q=belong[y];
	if(Lca==x) return make_pair(0,dep[y]-dep[x]);
	else if(Lca==y) return make_pair(dep[x]-dep[y],0);
	else if(Lca) return make_pair(dep[x]-dep[Lca],dep[y]-dep[Lca]);
	else
	{
		if(findroot(x)!=findroot(y)) return make_pair(-1,-1);
		int needx=dep[x]-dep[p],needy=dep[y]-dep[p],need1=num[p]-num[q],need2;
//		cout<<num[p]<<" "<<num[q]<<" ";
		if(need1<0) need1+=ring[vis[p]].size(),need2=num[q]-num[p];
		else need2=-need1+ring[vis[p]].size();
		need1+=needx,need2+=needy;
//		cout<<need1<<" "<<need2<<endl;
		if(max(need1,needy)>max(needx,need2)) return make_pair(needx,need2);
		else if(max(needx,need2)>max(need1,needy)) return make_pair(need1,needy);
		else
		{
			if(min(needx,need2)<min(needy,need1)) return make_pair(needx,need2);
			else if(min(needx,need2)>min(needy,need1)) return make_pair(need1,needy);
			else
			{
				if(need1>=needy) return make_pair(need1,needy);
				else return make_pair(needx,need2);
			}
		}
	}
}
signed main()
{
	init();
	prepare();
	for(int i=1;i<=m;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		pair<int,int> ans=get_ans(x,y);
		printf("%d %d\n",ans.first,ans.second);
	}
	return 0;
}

T3 Card Game

一道非常经典但是又极其恶心的综合了树上各大算法的好题。

首先,你得先把图给构造出来。

我们可以将一张卡片的正面与背面建一条边正连向背的单向边。那么显然,要让每一个点的出度都小于等于一才能符合题意,答案即为有向边要反转多少次和方案个数。

对于每个 弱联通块 我们分别进行求解:

  • 如果当前连通块边数大于点数,那么显然,所有点的出度之和大于点数,那么根据鸽巢原理,就无法满足条件,所以整个游戏他都无解。
  • 如果当前连通块边数小于点数,那么我们可以将这个图转为一棵树,建立无向边,并且若原有图中这条边,那么将其边权设为 1 1 1,否则设为 0 0 0。考虑定义 d p x dp_x dpx 表示 x x x 的出度为 0 0 0 时,需要修改的边的最小个数是多少。显然我们可以先找一个点作为跟,跑一遍 d p dp dp 在进行换根求解。最后要修改的个数即为对所有 d p dp dp 取最小值,方案个数即为有多少个 d p dp dp 值等于要修改的个数。( d p dp dp 转移式就自己想吧,这不是重点)。
  • 最后一种情况,及连通块边数等于点数,那么我们同第二种情况建立无向边,转化为无向图后,就可以得到一颗基环树。我们同样采用最开始说的思想一进行求解。首先对于一个环,我们将环上边权为 1 1 1 的个数和为 0 0 0 的个数求一个最小值即可得到要修改的个数,并且如果两种个数不等,方案就只有一个,否则就是两个。对于除开环的子树,我们对于每个子树的根跑一边 d p dp dp(不能换根,因为必须满足这个根的出度为0),然后将 d p dp dp 与环要修改的个数加起来,就是这种情况的总修改数,并且容易发现,方案数并不会变化。

代码写的有点冗长。

#include<bits/stdc++.h>
using namespace std;
#define maxe 100001
#define maxn 200001
#define mod 998244353
struct node
{
	int tar,nxt;
}arr[maxe*2];
int graphe_cnt,fst[maxn];
void adds(int x,int y) 
{
	arr[++graphe_cnt].tar=y,arr[graphe_cnt].nxt=fst[x],fst[x]=graphe_cnt;
}
int n,m;
unordered_set<long long> ep; 
vector<pair<int,int> > g[maxn];
vector<int> ltk[maxn],gg[maxn];
int fa[maxn],cnt[maxn],size[maxn],dp[maxn],cntt[maxn];
bool used[maxn],zz[maxn];
int isring[maxn],sum;
bool vis[maxn],has,is[maxn];
int edge,lastans,lastans2=1;
vector<int> ring;
int findroot(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=findroot(fa[x]);
}
void unionn(int x,int y)
{
	int p=findroot(x),q=findroot(y);
	if(p!=q) fa[p]=q,cnt[q]+=cnt[p],cntt[q]+=cntt[p],size[q]+=size[p];
}
void clear()
{
	memset(fst,0,(n+1)*4);
	memset(cnt,0,(n+1)*4);
	memset(cntt,0,(n+1)*4);
	memset(used,0,(n+1));
	memset(isring,0,(n+1)*4);
	memset(vis,0,(n+1));
	memset(dp,0,(n+1)*4);
	memset(is,0,(n+1));
	memset(zz,0,(n+1));
	graphe_cnt=0,edge=0,lastans=0,lastans2=1;
	ep.clear();
	for(int i=1;i<=n;++i) fa[i]=i,size[i]=1,ltk[i].clear(),g[i].clear(),gg[i].clear();
}
char gc(){static char buf[1000010],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000010,stdin),p1==p2)?EOF:*p1++;}
template<typename T>
void fast_read(T&x){x=0;bool f=0;static char s=gc();while(s<'0'||s>'9')f|=s=='-',s=gc();while(s>='0'&&s<='9')x=(x<<3)+(x<<1)+(s^48),s=gc();if(f)x=-x;}	
static char buf[1000005];int len=-1;
void flush(){fwrite(buf,1,len+1,stdout);len=-1;}
void pc(const char x){if(len==1000000)flush();buf[++len]=x;}
template<typename T>
void fast_write(T x){if(x<0)x=-x,pc('-');if(x>9)fast_write(x/10);pc(x%10^48);}
void input()
{
	fast_read(m);
	n=2*m;
	clear();
	for(int i=1;i<=m;++i)
	{
		int x,y;
		fast_read(x),fast_read(y);
		unionn(x,y);
		is[x]=true,is[y]=true;
		if(!ep.count(x*1000000ll+y))
		cnt[findroot(x)]++;
		else if(!ep.count(y*1000000ll+x))
		swap(x,y),lastans++,lastans2=lastans2*2%mod,cnt[findroot(x)]++; 
		adds(x,y);
		cntt[findroot(x)]++;
		ep.insert(x*1000000ll+y);
	}
}
int get_ring(int x,int last)
{
	if(isring[x]==1)
	{
		vis[x]=1,isring[x]=2,ring.push_back(x);
		return 1;
	}
	isring[x]=1;
	for(int i=0;i<g[x].size();++i)
	{
		int j=g[x][i].first;
		if(gg[x][i]==last) continue;
//		cout<<x<<" "<<j<<endl;
		if(get_ring(j,gg[x][i]))
		{
			if(isring[x]!=2)
			{
				vis[x]=1,isring[x]=2;
				ring.push_back(x);
				sum+=g[x][i].second;
				return 1;
			}
			else sum+=g[x][i].second;
			return 0;
		}
	}
	return 0;
}
pair<int,int> solve1(int fa)//点<边
{
	return make_pair(0,0); 
}
void dfs(int x,int last)
{
	if(zz[x]) return;
	zz[x]=1;
	for(int i=0;i<g[x].size();++i)
	{
		int j=g[x][i].first;
		if(last==gg[x][i]||vis[j]) continue;
		dfs(j,gg[x][i]);
		dp[x]+=dp[j]+g[x][i].second;
	}
}
void get_ans(int x,int last)
{
	if(zz[x]) return;
	zz[x]=1;
	for(int i=0;i<g[x].size();++i)
	{
		int j=g[x][i].first;
		if(gg[x][i]==last) continue;
		if(g[x][i].second)
		dp[j]=dp[x]-1;
		else
		dp[j]=dp[x]+1;
		get_ans(j,gg[x][i]);
	}
}
pair<int,int> solve2(int fa)//点=边
{
	if(cnt[fa]==1)
	{
		if(ltk[fa].size()==1) return make_pair(0,1);
//		return make_pair(1,max(cntt[fa],1));
	}
	int nowans=0,bj=0;
	for(int i=0;i<ltk[fa].size();++i)
	{
		int j=ltk[fa][i];
		for(int k=fst[j];k;k=arr[k].nxt)
		{
			g[j].push_back(make_pair(arr[k].tar,1));
			g[arr[k].tar].push_back(make_pair(j,0));
			gg[j].push_back(++edge);
			gg[arr[k].tar].push_back(edge);
		}
	}
	sum=0;
	get_ring(fa,0);
	nowans+=min(int(ring.size())-sum,sum);
	if(sum*2==ring.size()) bj=2;
	else if(ring.size()) bj=1;
//	cout<<ring.size()<<" "<<sum<<endl;
	for(int i=0;i<ltk[fa].size();++i) zz[ltk[fa][i]]=0;
	for(int i=0;i<ring.size();++i)
	{
		dfs(ring[i],0);
		nowans+=dp[ring[i]];
	}
//	cout<<nowans<<endl;
	ring.clear();
	return make_pair(nowans,bj);
}
pair<int,int> solve3(int fa)//点>边
{
	if(cnt[fa]==1)
	{
		if(cntt[fa]==1) return make_pair(0,1);
		return make_pair(1,max(cntt[fa],1));
	}
//	cout<<fa<<"-----------"<<endl;
	for(int i=0;i<ltk[fa].size();++i)
	{
		int j=ltk[fa][i];
//		cout<<j<<" ";
		for(int k=fst[j];k;k=arr[k].nxt)
		{
			++edge;
			g[j].push_back(make_pair(arr[k].tar,1));
			g[arr[k].tar].push_back(make_pair(j,0));
			gg[j].push_back(edge);
			gg[arr[k].tar].push_back(edge);
		}
	}
//	cout<<endl;
	dfs(fa,0);
	for(int i=0;i<ltk[fa].size();++i) zz[ltk[fa][i]]=0;
	get_ans(fa,0);
	int ans=INT_MAX,anss=0;
	for(int i=0;i<ltk[fa].size();++i)
	{
		int j=ltk[fa][i];
		if(ans>dp[j]) ans=dp[j],anss=1;
		else if(ans==dp[j]) anss++;
//		cout<<dp[j]<<" ";
	}
//	puts("");
//	cout<<ans<<endl;
	return make_pair(ans,anss);
}
pair<int,int> solve()
{
	for(int i=1;i<=n;++i) ltk[findroot(i)].push_back(i);
	pair<int,int> ans;
	ans.second=1;
	for(int i=1;i<=n;++i)
	{
		int p=findroot(i);
		if(!is[i]) continue;
		if(used[p]) continue;
		used[p]=true;
		pair<int,int> now;
		if(size[p]<cntt[p]) now=solve1(p);
		else if(size[p]==cnt[p]) now=solve2(p);
		else now=solve3(p);
		ans.first+=now.first;
		ans.second=1ll*ans.second*now.second%mod;
	}
	return ans;
}
signed main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		clear();
		input();
		pair<int,int> ans=solve();
		if(ans.second==0) puts("-1 -1");
		else printf("%d %d\n",ans.first+lastans,int(1ll*ans.second*lastans2%mod));
	}
	return 0;
}

代码略显丑陋。。。

T4 旅行

这个例题主要是写来具体第二种解题思想。

题目其实不难,每次把环上的任意一条边删掉。然后进行搜索求解,最终求得所有解的最大值即可。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int fst[5005],cnt;
inline int read()
{
	int flg=1,x=0;
	char c='\0';
	while(!isdigit(c)){if(c=='-')flg=-1;c=getchar();}
	while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*flg;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10^48);
}
pair<int,int> now;
struct node
{
	int tar,nxt;
}arr[10005];
void adds(int x,int y)
{
	arr[++cnt].tar=y,arr[cnt].nxt=fst[x],fst[x]=cnt;
}
priority_queue<int> p[5005];
void dfs(int x,int last)
{
	write(x);
	putchar(' ');
	for(int i=fst[x];i;i=arr[i].nxt)
	{
		int y=arr[i].tar;
		if(y==last) continue;
		dfs(y,x);
	}
}
int qwq;
vector<int> ans[5005],all;
bool vis[5005];
void dfs2(int x,int last)
{
	vis[x]=1;
	ans[qwq].push_back(x);
	for(int i=fst[x];i;i=arr[i].nxt)
	{
		int y=arr[i].tar;
		if(vis[y]) continue;
		pair<int,int> no1=make_pair(x,y),no2=make_pair(y,x);
		if(no1==now||no2==now) continue;
		dfs2(y,x);
	}
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;++i)
	{
		int x,y;
		x=read(),y=read();
		p[x].push(y),p[y].push(x);
	}		
	for(int i=1;i<=n;++i)
	{
		while(!p[i].empty())
		{
			adds(i,p[i].top());
			p[i].pop();
		}
	}
	if(m<n)
	{
		dfs(1,0);
	}
	else
	{
		for(int i=1;i<=n;++i)
		{
			for(int j=fst[i];j;j=arr[j].nxt)
			{
				int k=arr[j].tar;
				if(k<i) continue;
				now=make_pair(i,k);
				++qwq;
				memset(vis,0,sizeof(vis));
				dfs2(1,0);
			}
		}
		for(int i=1;i<=qwq;++i)
		{
//			for(int j=0;j<ans[i].size();++j) cout<<ans[i][j]<<" ";
//			puts("");
			if(ans[i].size()<n) continue;
			bool flg=0;
			if(!all.size())
			{
				all=ans[i];
				continue;
			}
			for(int j=0;j<n;++j)
			{
				if(all[j]>ans[i][j])
				{
					flg=1;
					break;
				}
				else if(all[j]<ans[i][j])
				{
					break;
				}
			}
			if(flg==true)
			all=ans[i];
		}
		for(int i=0;i<all.size();++i)
		{
			write(all[i]);
			putchar(' ');
		}
	}
	return 0;
}

4.一些心得

其实看了上面的题解可以发现,基环树其实解题思想并没有那么难,主要是真的很难调。。。

有一个很难处理的地方,就是重边,一般调不出来 99% 都是死在这里了。有些重边会对答案造成影响,所以在搜索时不能传统的去判断,而要写当前遍历到的边是否与上一条边是等效的,所以这里推荐写链式前向星,即下面这行代码:

if(i==((last-1)^1)+1) continue;//i为上一条,j为这一条,且边是从1开始存的

另外,无向图处理环的时候,记得要把环在想后面复制一份,因为环他有两种行走的方向。

最后,基环树是一种综合性很强的东西,再调他的时候,相信它一定可以增强你的心理承受能力的!


T h e   E n d \Huge\mathscr{The\ End} The End

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/742852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Acwing.861 二分图的最大匹配(匈牙利算法)

题目 给定一个二分图&#xff0c;其中左半部包含nq个点(编号1n1)&#xff0c;右半部包含ng个点(编号1n2)&#xff0c;二分图共包含m条边。 数据保证任意—条边的两个端点都不可能在同一部分中。请你求出二分图的最大匹配数。 给定一个二分图G&#xff0c;在G的一个子图M中&…

软件测试基础认知

日升时奋斗&#xff0c;日落时自省 目录 1、测试需求 2、测试用例 3、软件测试BUG 4、开发模型 4.1、软件的生命周期 4.2、瀑布模型&#xff08;waterFall Model&#xff09; 4.3、螺旋模型 4.4、 增量模型 4.4.1、增量开发 4.4.2、迭代开发 4.5、敏捷开发&#xf…

【PDFBox】PDFBox操作PDF文档之创建PDF文档、加载PDF文档、添加空白页面、删除页面、获取总页数、添加文本内容、PDFBox坐标系

这篇文章&#xff0c;主要介绍PDFBox操作PDF文档之创建PDF文档、加载PDF文档、添加空白页面、删除页面、获取总页数、添加文本内容、PDFBox坐标系。 目录 一、PDFBox组件 1.1、什么是PDFBox 1.2、创建PDF文档 1.3、加载PDF文档 1.4、添加空白页面 1.5、删除某个页面 1.6…

三分钟了解 SpringBoot 的启动流程

一、前言 背景&#xff1a;最近有位开发同学说面试被问到Spring Boot 的启动流程&#xff0c;以及被问到Spring Boot 的嵌入式Web容器是什么时候加载的。如何加载的。是怎么无缝切换的。 这些问题&#xff0c;其实回答起来也是比较复杂的。我们今天就从 SpringApplication.ru…

3D设计建模软件The Foundry Modo 16对Mac和Windows的系统要求

Foundry MODO是一款功能强大的三维建模、动画和渲染软件。它为艺术家和设计师提供了一套全面的工具&#xff0c;可以用来创建令人惊叹的视觉效果。无论是制作电影、电视节目、游戏还是其他数字媒体内容&#xff0c;MODO都可以满足您的需求。 MODO具有直观的用户界面&#xff0…

redis主从配置

从redis配置&#xff1a;redis6386.conf include /data/redis/redis6380.conf #主redis配置文件路径&#xff08;这里是引用主配置文件里的配置在修改从配置&#xff09; daemonize yes #在后台启动 protected-mode no #加密保护关闭 bind 192.168.3.*** requirepas…

云主机安全-私有密钥安全认证

场景描述 云主机凭借其性价比高、生配扩容便利、运维便捷、稳定性高等优势深受用户青睐&#xff0c;越来越多的企业开始租用云主机&#xff0c;将自己的服务器、业务系统等搭建或存储到云主机上。 用户痛点 用户租用或托管的云主机&#xff0c;运维端口&#xff08;远程桌面&…

SSM框架最新整合保姆级教程(IDEA版)

SSM框架最新整合保姆级教程(IDEA版) 一、环境要求 ​ 环境&#xff1a; IDEAMySQL 5.7.19Tomcat 9Maven 3.6 要求&#xff1a; 需要熟练掌握MySQL数据库&#xff0c;Spring&#xff0c;JavaWeb及MyBatis知识&#xff0c;简单的前端知识&#xff1b; 完整代码&#xff1a;…

Openlayers实战:绘制带箭头的线

Openlayers地图中有的时候会用到这样的场景,连续画几段线段,但是要知道绘制的方向,给人以指引的提示作用。 怎么绘制呢? 在本实战中,主要的是处理线段的显示方式,在线段的拐点处附加上箭头图片,具体看实际的源代码。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhu…

4 生成全排列

4 生成全排列 作者: 赵晓鹏时间限制: 1S章节: 递归与分治 输入范例 : 无 输出范例 : Perm1(1):123 Perm1(2):123 Perm1(2):213 Perm1(2):321 Perm1(3):123 Perm1(3):132 Perm1(3):213 Perm1(3):231 Perm1(3):321 Perm1(3):312 Online Judge 1.0 #include<iostream> …

【全栈开发指南】数据权限使用配置

数据权限配置有两种方式&#xff1a; 通过系统配置界面&#xff0c;实时配置生效。 通过代码注解配置。 一、通过系统配置界面配置数据权限 系统配置的数据权限是通过系统配置界面将配置信息保存在数据库&#xff0c;然后系统启动时&#xff0c;将配置信息保存到Redis缓存来…

SpringCloud分布式搜索引擎、数据聚合、ES和MQ的结合使用、ES集群的问题

目录 数据聚合 聚合的分类 ​编辑 DSL实现Bucket聚合 ​编辑 DSL实现Metrics聚合​编辑 RestAPI实现聚合 对接前端接口​编辑 自定义分词器​编辑 Completion suggester查询 Completion suggester查询 酒店数据自动补全 实现酒店搜索框界面输入框的自动补全 数据同步问…

Codeforces Round 875 (Div. 1) A. Copil Copac Draws Trees

题意 Copil Copac 给定了一个由 n−1 条边组成的列表&#xff0c;该列表描述了一棵由 n 个顶点组成的树。他决定用下面的算法来绘制它&#xff1a; 步骤 0&#xff1a;绘制第一个顶点&#xff08;顶点1&#xff09;。转到步骤1。 步骤 1&#xff1a;对于输入中的每一条边&#…

Window10 系统 RabbitMQ的安装和简单使用

1、下载 & 安装 Erlang 因为RabbitMQ的服务端是基于 Erlang编写的&#xff0c;所以&#xff0c;首先需要安装Erlang。 1&#xff09;下载 下载地址如下&#xff1a; https://www.erlang.org/downloads此处下载比较慢&#xff0c;可以参考如下百度网盘&#xff1a; 链接…

常用的缓存工具有ehcache、memcache和redis,这里介绍spring中ehcache的配置。

常用的缓存工具有ehcache、memcache和redis&#xff0c;这里介绍spring中ehcache的配置。 1.在pom添加依赖&#xff1a; <!-- ehcache 相关依赖 --><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><ve…

正则表达式测试(二)

一、中括号的语法 匹配所有的字符&#xff0c;返回一个数组,包含匹配的所有字符内容&#xff0c;按顺序展开&#xff1b; 注意&#xff1a;空格也会被匹配到 匹配所有符合的字符&#xff0c;返回一个数组。 匹配空白字符 匹配非空白字符 匹配 空白字符 非空白字符 如上所示&am…

二叉树的简单遍历

假设节点数据类如下&#xff1a; public class TreeNode {String val;TreeNode left;TreeNode right;TreeNode() { }TreeNode(String val) {this.val val;}TreeNode(String val, TreeNode left, TreeNode right) {this.val val;this.left left;this.right right;} } 二叉…

Vue3+TS+Vite创建项目,并导入Element-plus和Sass

一、vue3创建项目 1.桌面新建一个文件夹Vue3-app 打开编辑器导入文件夹&#xff0c;编辑器打开终端输入或者命令行工具cd到项目目录下输入 npm init vuelatest 回车运行 这里我选择了TSVite来开发&#xff0c;并选择安装路由 2.cd到 vue-project目录下 输入 npm install 回车…

路径规划算法:基于晶体结构优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于晶体结构优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于晶体结构优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

基于高斯混合模型聚类的风电场短期功率预测方法(Pythonmatlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…