题意解析
题目给了n个点,n条边.那也就是说给了一个基环树.
一个树上的每两个点之间只有一条边,也就是n个点,n-1条边.
现在多给一条边,那么就会在树里形成一个环,我们就称之为基环树:
题目就是让我们求这个基环树的简单路径的个数.
什么是简单路径?
我们再看一下基环树上的简单路径,点A到点B就有两条路径:
点C到点D也有两道路径:
难道说基环上所有两个点之间都有2条路径吗?并不是,我们可以看见点E到点F只有一条路径:
我们发现点F并不在环上,同理我们可以得出任何不在环上的点的最简路径个数都是1
因此我们可以得出一个结论:求基环树的最简路径的个数要求在环上的节点的最简路径的个数+不在环上的最简路径的个数
解题思路1
解题思路2
我们每次都把只连接一个点的节点加入到队列中.
其实就是把不在环上的点加入到队列中,这些点全是叶子节点:
然后我们看一下当前点t是不是叶子节点(即是不是只与1个点1相连):
如果是叶子节点,就向上路径个数,然后删掉自己:
然后我们再看当前点t是不是叶子节点,发现不是,那我们就找当前点相连的叶子节点,找到叶子节点之后接着向上传递,然后删除叶子节点:
接着看当前点t是否是叶子节点,发现是,那么向上传递,删除自身:
那么现在我们就消掉一个子树了,我们只需要把所有的子树全部消掉,最后就只有一个环了,我们就只需要求环上的最简路径的个数即可:
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;
}
需要注意的是这里用的vector<set> arr(n)而不是vector<vector> arr(n)
因为下面涉及到了删除操作,vector是无序的,如果要查找删除的话复杂度是O(n),而set是有序的,查找删除只需要O(logn)
这样使用set的整体复杂度为O(nlogn),而使用vector的整体复杂度为