图论基础:欧拉路,拓扑排序,树的直径与重心

欧拉路

首先结论:

一个图存在欧拉路则每个点的入度等于出度或者一个点的入度比出度大一(终点),一个点的入度比出度小一(起点),其他点入度等于出度。

然后是朴实无华的爆算。

void dfs(int x){
    for(int &i=head[x];i;i=head[x]){//这里推荐这么写 机房某大佬卡了不这么写而是修改前向星的写法
        dfs(edge[i].v);
    }
    s.push(x);
}

当然,我们发现这个递归的深度是\(O(m)\)级别的。所以我们似乎可以模拟系统栈来防止mle的发生。大概就像这样(其实没怎么改)

void euler(int st){
	s.push(st);
	while(!s.empty()){
		int x=s.top(),i=head[x];
		while(i&&v[i])i=edge[i].next;
		if(i){
			s.push(edge[i].v);
			v[i]=v[i^1]=true;
			head[x]=edge[i].next;
		}
		else{
			s.pop();ans.push(x);
		}
	}
}

然后是另一个小小的东西:拓扑排序。(这几个我放在一起纯属因为篇幅太小都不到一百行)

大概是处理DAG图的一种类似bfs的方式,但是有些不同。首先我们建一个栈,统计所有点的入度,并将入度为0的点入栈。然后我们依次对栈中所有点实行操作:

  1. 出栈,扫描所有能连到的点,使其入度-1;
  2. 如果入度为0则入栈。

如此重复,直到栈为空。代码也很简单。(我这个拿vector存的图,懒得改了)

int dgr[114514];
stack<int>s;
void tuopu(){
    for(int i=1;i<=n;i++){
        if(dgr[i]==0)s.push(i);
    }
    while(!s.empty()){
        int v=s.top();s.pop();
        printf("%d ",v);
        for(i=1;i<=in[v][0];i++){
            dgr[in[v][i]]--;
            if(dgr[in[v][i]]==0)s.push(in[v][i]);
        }
    }
}

树的直径。

定义树的直径是树上的最长链(或者它的长度)。

性质:

  1. 直径两端点是叶子。(废话)
  2. 某个距离任意点最远的点一定是直径某端点。(也就是下文两次爆搜求直径的原理)
  3. 对两棵树,若第一棵直径两端点为\(u,v\),第二棵为\(x,y\),两棵树用一条边连起来,那么新树的直径一定是u,v,x,y中两个点。
  4. 对一棵树,若一个点上连一个叶子节点,则最多改变直径一个端点。
  5. 若一棵树存在多条直径,那么它们交于一点且为直径中点。

两种解法:

  1. 树形dp

大概就是统计每个节点子树的最长链和次长链然后加起来统计答案,比较显然。

void dfs(int x,int f){
	dp[x][0]=dp[x][1]=0;//0是当前节点最长的子链 1是次长 
	for(int i=head[x];i;i=edge[i].next){
		if(edge[i].v!=f){
			dfs(edge[i].v,x);
			if(dp[edge[i].v][0]+edge[i].w>dp[x][0]){
				dp[x][1]=dp[x][0];
				dp[x][0]=dp[edge[i].v][0]+edge[i].w;
			}
			else if(dp[edge[i].v][0]+edge[i].w>dp[x][1])dp[x][1]=dp[edge[i].v][0]+edge[i].w;
			ans=max(ans,dp[x][0]+dp[x][1]);
		}
	}
}
  1. 两次爆搜

按照定义,树的直径是树上的最长链。也就是说,我们从任意节点开始搜,距离该节点最远的节点一定是直径的一个端点。然后再爆搜一次就行了。注意这个方法没法处理负权边。

void dfs(int x,int f){
	for(int i=head[x];i;i=edge[i].next){
		if(edge[i].v!=f){
			dis[edge[i].v]=dis[x]+1;
			if(dis[edge[i].v]>dis[zj])zj=edge[i].v;
			dfs(edge[i].v,x);
		}
	}
}
dfs(1,0);
d[zj]=0;dfs(zj,0);
ans=d[zj];

树的重心

定义:对于树上的每一个点,计算其为根时所有子树中最大的子树节点数,值最小的点就是这棵树的重心。

性质:

  1. 只有以重心为根时,所有子树大小都不超过整棵树大小的一半。
  2. 树上所有点到某个点距离和中,到重心距离和最小。如果有多个重心则相同。
  3. 两棵树之间连一条边,新树的重心在原树两重心的链上。
  4. 在一棵树上添加或者删除一个叶子,树的重心最多移动一条边的距离。
  5. 一棵树最多两个重心,且相邻。

求法:简单定义法。统计子树最大值,选最小的。

void find(int x,int fa){
	size[x]=1;maxx[x]=0;
	for(int i=head[x];i;i=edge[i].next){
		if(edge[i].v!=fa){
			find(edge[i].v,x); 
			size[x]+=size[edge[i].v]; 
			maxx[x]=max(maxx[x],size[edge[i].v]);//统计最大子树规模 
		}
	}
	maxx[x]=max(maxx[x],sum-size[x]);//还有自己上边的子树 
	if(maxx[x]<maxx[rt])rt=x;//更新答案 
}
maxx[0]=2147483647;sum=n;
posted @ 2022-09-03 11:47  gtm1514  阅读(73)  评论(0编辑  收藏  举报