coderforces 1454E 求基环树的最简路径的个数

86 阅读3分钟

Problem - 1454E - Codeforces

image.png

题意解析

题目给了n个点,n条边.那也就是说给了一个基环树.

一个树上的每两个点之间只有一条边,也就是n个点,n-1条边.

现在多给一条边,那么就会在树里形成一个环,我们就称之为基环树:

image.png

题目就是让我们求这个基环树的简单路径的个数.

什么是简单路径?

image.png

image.png

我们再看一下基环树上的简单路径,点A到点B就有两条路径:

image.png

点C到点D也有两道路径:

image.png

难道说基环上所有两个点之间都有2条路径吗?并不是,我们可以看见点E到点F只有一条路径:

image.png

我们发现点F并不在环上,同理我们可以得出任何不在环上的点的最简路径个数都是1

因此我们可以得出一个结论:求基环树的最简路径的个数要求在环上的节点的最简路径的个数+不在环上的最简路径的个数

解题思路1

image.png

解题思路2

我们每次都把只连接一个点的节点加入到队列中.

其实就是把不在环上的点加入到队列中,这些点全是叶子节点:

image.png

然后我们看一下当前点t是不是叶子节点(即是不是只与1个点1相连):

image.png

如果是叶子节点,就向上路径个数,然后删掉自己:

image.png

然后我们再看当前点t是不是叶子节点,发现不是,那我们就找当前点相连的叶子节点,找到叶子节点之后接着向上传递,然后删除叶子节点:

image.png

接着看当前点t是否是叶子节点,发现是,那么向上传递,删除自身:

image.png 那么现在我们就消掉一个子树了,我们只需要把所有的子树全部消掉,最后就只有一个环了,我们就只需要求环上的最简路径的个数即可:

image.png

code

#include<bits/stdc++.h>
using namespace std;
typedef  long long LL;
int main()
{
	cin.tie(nullptr)->sync_with_stdio(0);
	int t;cin>>t;
	
	while(t--)
	{
		LL n;cin>>n;
		vector<set<int>> arr(n);
		int point[n];
		
		//建基环树 
		for(int i=0;i<n;i++)
		{
			int a,b;cin>>a>>b;
			a--,b--;
			point[i]=1;
			arr[a].insert(b);
			arr[b].insert(a);
		}
		
		//把只连接一个点的节点加入到队列中 
		queue<int> q;
		for(int i=0;i<n;i++)
		{
		  if(arr[i].size()==1) q.push(i);	
	    }
	    
	    while(!q.empty())
		{
		  int t=q.front();  //把点拿出来
		  q.pop();
		  int g=*arr[t].begin(); //求当前点唯一连接的那个点
		  point[g]+=point[t];    //连接的那个点的孩子节点+1
		  arr[t].clear();   //清楚这个点的影响
		  arr[g].erase(t);  //删除当前这个点
		  if(arr[g].size()==1)
		  {
		  	q.push(g);
	      } 	
		} 
		
		LL ans=n*(n-1);
		for(int i=0;i<n;i++)
		{
			int z=arr[i].size();
			if(z==0)continue;
			LL l=point[i];
			ans-=l*(l-1)/2;  //删除重复的影响 
		}
		cout<<ans<<endl;
	}
	
	return 0;
}

image.png

需要注意的是这里用的vector<set> arr(n)而不是vector<vector> arr(n)

因为下面涉及到了删除操作,vector是无序的,如果要查找删除的话复杂度是O(n),而set是有序的,查找删除只需要O(logn)

这样使用set的整体复杂度为O(nlogn),而使用vector的整体复杂度为O(n2)O(n^{2})