题意:
给你一个有n个点的树,给定根,叫你找第k大的特殊链
特殊的链的定义:u,v之间的路径,且lca(u,v)!=u&&lca(u,v)!=v,且路径第k大
思路:转自http://bestcoder.split.hdu.edu.cn/solutions.php?page=2
对于求第k大的问题,我们可以通过在外层套一个二分,将其转化为求不小于mid的有多少个的问题。接下来我们讨论如何求树上有多少条折链的长度不小于k。
我们考虑常规的点分治(对于重心,求出其到其他点的距离,排序+单调队列),时间复杂度为O(nlog^2n),但是这只能求出普通链的数量。
我们考虑将不属于折链的链容斥掉。也即,我们需要求出有多少条长度不小于mid的链,满足一端是另一端的祖先。设有一条连接u,v的链,u是v的祖先。
我们设 d[i] 为从根到i的链的长度,然后枚举v,然后计算在从根到v的链上,有多少个点i满足 d[v]−dist[i]≥mid
我们可以按照dfs序访问各结点,动态维护从根到其的链上各d值构成的权值树状数组,就能够计算这种链的数量。时间复杂度为O(nlogn)。 因此求长度不小于mid的折链数量可以在O(nlog2n)的时间复杂度内完成。再套上最外层的二分,总时间复杂度为O(nlog3n)。
n的范围是50000,时限6s,卡常数就过去了(本行划线 由于在点分治中,复杂度中第二个logn的瓶颈在于排序。由于每次排序都是对相同的数排序,因此我们可以考虑将点分治+排序作为预处理,每次二分的时候只要做单调队列部分即可。
上述做法的总时间复杂度为O(nlog2n)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int tot,head[maxn],size[maxn],root,f[maxn],id,Count,Ans,sz;//f[i]表示i这个子树的重心
bool Del[maxn];
int n,t[maxn],cnt,Root;;
vector<int>G[maxn*10],que[maxn];
struct Edge{
int to,next,cost;
}e[maxn];
void init(){
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w){
e[tot].to=v;
e[tot].next=head[u];
e[tot].cost=w;
head[u]=tot++;
}
void getroot(int u,int pre){
size[u]=1,f[u]=0;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(v!=pre&&!Del[v]){
getroot(v,u);
size[u]+=size[v];
f[u]=max(f[u],size[v]);
}
}
f[u]=max(f[u],Count-size[u]);
if(f[u]<f[root]) root=u;
}
void getdeep(int u,int pre,int dep){
G[cnt].push_back(dep);
size[u]=1;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(v!=pre&&!Del[v]){
getdeep(v,u,dep+e[i].cost);
size[u]+=size[v];
}
}
}
long long Cal(int u,int dep){
++cnt;
getdeep(u,0,dep);
sort(G[cnt].begin(),G[cnt].end());
}
void Work(int u){
Cal(u,0);//表示链的两边的能为tmp
Del[u]=true;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(!Del[v]){
Cal(v,e[i].cost);
f[0]=Count=size[v];//表示这个子树的根
getroot(v,root=0);
que[u].push_back(root);
Work(root);
}
}
}
void Init(){
for(int i=1;i<=n;i++)
que[i].clear();
for(int i=1;i<=10*n;i++)
G[i].clear();
memset(Del,false,sizeof(Del));
f[0]=Count=n;
getroot(1,root=0),cnt=0;
Root=root;
Work(root);
}
//相当于把排序去掉
long long cal(int mid){
long long ret=0;
++id;
int num=G[id].size()-1,r=num;
for(int i=0;i<num;i++){
r=max(i+1,r);
while(r>i+1&&G[id][r-1]+G[id][i]>=mid) r--;
if(G[id][r]+G[id][i]<mid)
continue;
ret+=(num-r+1);
}
return ret;
}
void work(int u,int mid){
Ans+=cal(mid);//表示链的两边的能为tmp
int x=0;
Del[u]=true;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(!Del[v]){
Ans-=cal(mid);
work(que[u][x++],mid);
}
}
}
int C[maxn],m,TOT;
void add(int x,int num){
while(x<=TOT)
C[x]+=num,x+=(x&-x);
}
int sum(int x){
int ret=0;
while(x>0)
ret+=C[x],x-=(x&-x);
return ret;
}
void dfs(int u,int dep,int pre,int mid){
t[++sz]=dep;
t[++sz]=dep-mid;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(v!=pre)
dfs(v,dep+e[i].cost,u,mid);
}
}
void Dfs(int u,int dep,int pre,int mid){
int num1=lower_bound(t+1,t+TOT+1,dep-mid)-t,num2=lower_bound(t+1,t+TOT+1,dep)-t;
Ans-=sum(num1);
add(num2,1);
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(v!=pre)
Dfs(v,dep+e[i].cost,u,mid);
}
add(num2,-1);
}
bool check(int mid,int k){
Ans=0,memset(Del,false,sizeof(Del));
id=0,work(Root,mid);
memset(C,0,sizeof(C));
sz=0,dfs(m,0,0,mid);
sort(t+1,t+sz+1);
TOT=unique(t+1,t+sz+1)-t-1;
Dfs(m,0,0,mid);
if(Ans>=k) return true;
return false;
}
int main(){
int _,k;
scanf("%d",&_);
while(_--){
scanf("%d%d%d",&n,&m,&k);
int maxv=0,u,v,w;
init();
for(int i=1;i<n;++i){
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w),addedge(v,u,w);
maxv=max(maxv,w);
}
Init();
//check(18,1);
int low=0,high=maxv*n,ans=0;
while(high-low>=0){
int mid=(low+high)>>1;
if(check(mid,k)) low=mid+1,ans=mid;
else high=mid-1;
}
if(ans==0) printf("NO\n");
else printf("%d\n",ans);
}
return 0;
}