题意:T组数据,每组数据第一行两个数n,k。n表示点的个数,接下来n-1行每行两个数,表示两个点的连线,形成简单无环图。在这个图中找到任意两个点的距离有n*(n-1)/2个距离从小到大排序。取前k项的和。
官方题解:
把所有边(u,v) 以及(v,u)放入一个队列,队列每弹出一个元素(u,v),对于所有与u相邻的点w,如果w!=v,就把(w,u)入队。这样就能一个一个生成前K小的距离。 注意到每条边实际上会入队两次,只要把K翻倍且把ans除2即可,时间复杂度为O(n+K);
这里只是实现一下而已。
AC代码:
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
typedef long long LL;
const ll maxn=100050;
int head[maxn+7],tot=0; ///head是邻接表中的头结点的定义,邻接表中节点的个数
ll ans,k,n;
struct node///存储到队列中表示从u点到v点路径的长度为d
{
int u,v,d;
node(int _u,int _v,int _d):u(_u),v(_v),d(_d){}
node(){}
};
struct endge///邻接表中的边定义
{
int v,next;
}G[maxn<<2+7];///因为建图时要双向加边所以要开两倍
void addeduge(int u,int v)///加边操作,u为头节点
{
G[tot].v=v;
G[tot].next=head[u];
head[u]=tot++;
}
queue <node> q;
void bfs()
{
int cnt=0;///表示已经找的的最小项的个数
while(!q.empty())
{
if(cnt>=k) break;
node tmp=q.front();
q.pop();
int u=tmp.u;
int v=tmp.v;
int d=tmp.d;
if(cnt>=k) break;
for(int i=head[u];i!=-1;i=G[i].next)
{
int vv=G[i].v;
if(vv!=v)
{
ans+=d+1;
cnt++;
q.push(node(vv,u,d+1));///查找完当前边之后路径长度加1
}
if(cnt>=k) break;
}
if(cnt>=k) break;
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
while(!q.empty())///清空队列
q.pop();
memset(head,-1,sizeof(head));
tot=0;
ans=0;
scanf("%I64d%I64d",&n,&k);
int u,v;
for(int i=1;i<=n;i++) q.push(node(i,i,0));///将每个点的本身加入队列
for(int i=0;i<n-1;i++)
{
scanf("%d%d",&u,&v);
addeduge(u,v);
addeduge(v,u);///双向加边
}
k*=2;///每个边的路径加了两次,所以将查找的边个数乘2.
bfs();
printf("%I64d\n",ans/2);///答案也应该除2
}
return 0;
}