题意:
给你一棵
n
n
n个点,边有边权的无根树,给你一个
l
l
l和一个
r
r
r,让你求一条经过的边数在
[
l
,
r
]
[l,r]
[l,r]之间的路径,使得将路径上所有边按照权值排名,排名在的中间的权值尽可能大,输出路径的两个端点。这里与数学上的中位数是有区别的,要求如果边数是奇数,那么就是数学上的中位数,如果是偶数,那么是排名在中间的相邻两个数较大的那一个,而不是平均值。
n
<
=
1
e
5
n<=1e5
n<=1e5。
题解:
这个题真的是整了我一天。昨天上午想了和写了四五个错误做法,下午研究了一下午题解,晚上连写带调搞了一晚上,今天早上又调了一个小时,终于过掉这个CF3000分的题了。
下面说一下这个题的做法。首先我们发现这个类似中位数的东西并不好直接做,但是这种题我们通常会去二分答案,然后把边权转化成 1 1 1或 − 1 -1 −1,看有没有边数在 [ l , r ] [l,r] [l,r]并且路径权值和大于等于 0 0 0的路径就可以了。对于这种树上路径的问题,我们经常是去考虑点分治。对于这种点分治加二分的题,我经常把二分写在点分治里面,感觉常数应该会小一些。当然为了让常数更小一点,我还把边权离散化了一下,这样二分的上界变小了很多。
之后我们考虑怎么check二分的答案是否可行。我们的思路是对于当前分治点,一个子树一个子树的去考虑,算完当前子树后与之前已经考虑过的子树的情况进行合并。我们对当前子树dfs一遍,求出所有可能的边数与权值和,还要记录一下这条边的端点,更新答案的时候要用。我们想开一个桶去记录之前每一个边的数量的最优答案,这样的话当前子树每条可能的路径与之前可能组成合法路径的是一个边数区间内的所有路径。对于这个问题,我们的想法是对于子树的所有路径按照边数排序,这样就可以对于之前的子树维护一个单调队列来回答每一次当前子树某个边数对应的合法区间的最优答案了。但是这里我们发现单调队列维护的时候,到底怎么样算是一个更优的答案呢?其实有一种很好的方法是,不去把边权设为 1 1 1和 − 1 -1 −1,而是设为 2 2 2和 0 0 0,这样我们维护边权和减去边数的最大值即可,这个最大的一定是拼过来的链能合法的情况下,需要拼接过来的链权值尽可能小的。但是这样复杂度还是有些问题,我们要在分治的每一层复杂度是 O ( 当 前 层 点 数 ) O(当前层点数) O(当前层点数)的才行,这样如果你一开始加进来了一个很大的子树,之后每次单调队列都会扫一遍这个很大的子树,复杂度就不对了。那么我们应该如何保证复杂度呢?方法是先dfs一遍所有子树,并按照最大深度排序,从最大深度最小的先开始考虑。这样复杂度是对的的原因是,我们考虑当前子树的时候,单调队列所扫过的深度一定是不超过当前子树深度的,也就一定不可能超过当前子树大小,这样考虑每棵子树的时候复杂度瓶颈都是当前子树大小的,于是考虑完整个一层之后复杂度就是总的这一层的点数之和,复杂度就对了。
这样的话思路和做法就讲完了,细节还是不少的。复杂度的话,点分治一个 l o g log log,二分答案一个 l o g log log,我在二分之后其实统计每棵子树到当前分治中心的路径的时候还按照深度排了个序来保证单调队列的正确性,于是还有一个 l o g log log,但是这个 l o g log log是可以在当前子树上bfs来去掉的。那个给子树按照深度排序的 l o g log log可以用点分治结构内二分,并且在二分之前先处理处来的方式去掉,所以这个题我写的复杂度是 O ( n l o g 3 n ) O(nlog^3n) O(nlog3n)的,但是是可以做到 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的。实测的话我的写法确实不太优秀,跑得挺慢的。代码比较长。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,le,ri,hed[100010],cnt,mx[100010],sz[100010],rt,vis[100010],ans;
int ji1=1,ji2=1,num,shu,f[100010],g[100010];
int premax,ggg,gggg[100010],maxdep,dep[100010],mxdep[100010];
struct node
{
int to,next,dis;
}a[200010];
struct qwq
{
int len,dis,ji;
}b[200010],e[2000010];
struct edge
{
int x,y,z;
}QAQ[200010];
struct ymh
{
int x,dis;
}c[100010];
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void add(int from,int to,int dis)
{
a[++cnt].to=to;
a[cnt].dis=dis;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline void getrt(int x,int f,int size)
{
sz[x]=1;
mx[x]=0;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(vis[y]||y==f)
continue;
getrt(y,x,size);
sz[x]+=sz[y];
mx[x]=max(mx[x],sz[y]);
}
mx[x]=max(mx[x],size-sz[x]);
if(mx[x]<mx[rt])
rt=x;
}
inline void dfs(int x,int f)
{
mxdep[x]=dep[x];
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(y==f||vis[y])
continue;
dep[y]=dep[x]+1;
dfs(y,x);
mxdep[x]=max(mxdep[x],mxdep[y]);
}
}
inline int cmp(ymh x,ymh y)
{
return mxdep[x.x]<mxdep[y.x];
}
inline void dfs2(int x,int f,int dis,int len,int mid)
{
maxdep=max(maxdep,len);
b[++num].len=len;
b[num].dis=dis;
b[num].ji=x;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(y==f||vis[y])
continue;
if(a[i].dis>=mid)
dfs2(y,x,dis+2,len+1,mid);
else
dfs2(y,x,dis,len+1,mid);
}
}
inline int cmp1(qwq x,qwq y)
{
return x.len<y.len;
}
inline int check(int x,int mid)
{
e[0].ji=x;
int pd=0;
premax=0;
for(int i=1;i<=shu;++i)
{
num=0;
int y=c[i].x;
maxdep=0;
if(c[i].dis>=mid)
dfs2(y,x,2,1,mid);
else
dfs2(y,x,0,1,mid);
sort(b+1,b+num+1,cmp1);//在每一个子树上bfs可以省去排序
deque<int> q;
int cur=premax;
for(int j=1;j<=num;++j)
{
while(b[j].len+e[cur].len>=le&&cur>=0)
{
while(!q.empty())
{
if(e[q.back()].dis-e[q.back()].len<e[cur].dis-e[cur].len)
q.pop_back();
else
break;
}
q.push_back(cur);
--cur;
}
while(!q.empty())
{
if(e[q.front()].len+b[j].len>ri)
q.pop_front();
else
break;
}
if(!q.empty())
{
if(e[q.front()].dis-e[q.front()].len+b[j].dis-b[j].len>=0)
{
ji1=e[q.front()].ji;
ji2=b[j].ji;
pd=1;
break;
}
}
}
if(pd==1)
break;
for(int j=1;j<=num;++j)
{
if(b[j].dis-b[j].len>e[b[j].len].dis-e[b[j].len].len||e[b[j].len].len==0)
e[b[j].len]=b[j];
}
premax=maxdep;
}
for(int i=0;i<=premax;++i)
{
e[i].dis=0;
e[i].len=0;
e[i].ji=0;
}
return pd;
}
inline void solve(int x,int size)
{
vis[x]=1;
shu=0;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(vis[y])
continue;
dep[y]=1;
dfs(y,0);
c[++shu].x=y;
c[shu].dis=a[i].dis;
}
sort(c+1,c+shu+1,cmp);
int l=ans+1,r=n,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(check(x,mid))
{
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(vis[y])
continue;
int gg=sz[y];
if(sz[y]>sz[x])
gg=size-sz[x];
rt=0;
getrt(y,0,gg);
solve(rt,gg);
}
}
int main()
{
n=read();
le=read();
ri=read();
for(int i=1;i<=n-1;++i)
{
QAQ[i].x=read();
QAQ[i].y=read();
QAQ[i].z=read();
gggg[i]=QAQ[i].z;
}
sort(gggg+1,gggg+n);
ggg=unique(gggg+1,gggg+n)-gggg-1;
for(int i=1;i<=n-1;++i)
QAQ[i].z=lower_bound(gggg+1,gggg+ggg+1,QAQ[i].z)-gggg;
for(int i=1;i<=n-1;++i)
{
int x=QAQ[i].x,y=QAQ[i].y,z=QAQ[i].z;
add(x,y,z);
add(y,x,z);
}
e[0].len=0;
e[0].dis=0;
mx[0]=2e9;
getrt(1,0,n);
solve(rt,n);
printf("%d %d\n",ji1,ji2);
return 0;
}