【BZOJ3784】树上路径

题目大意

给定一个\(N\)个结点的树,结点用正整数\(1..N\)编号。每条边有一个正整数权值。用\(d(a,b)\)表示从结点\(a\)到结点\(b\)路边上经过边的权值。其中要求\(a < b.\)将这\(n*(n-1)/2\)个距离从大到小排序,输出前\(M\)个距离值。

题目分析

统计树上路径的问题显然需要淀粉质(好毒瘤啊,连续考了两天点分治)。

由于前\(M\)大路径难以直接统计,而我们又很擅长统计长度大于\(l\)的路径个数,因此考虑首先二分答案求出第\(M\)大路径的长度\(l\),再统计长度大于\(l\)的路径。

分析一波时间复杂度,点分\(O(n\ logn)\),统计路径时为了方便,我们需要排序后二分(又一个\(logn\)),本身二分答案就是\(logn\),这样一来时间复杂度达到了\(O(n\ log^3n)\),难以接受。

考试的时候想到一些小优化,如把点分后的每一个子树的根记录下来。当时也想到了把路径存下来,但不知道为什么就认为空间复杂度不正确立马否定掉了。实际上就是要将路径存下来,而且空间复杂度为\(O(n\ logn)\)。这样我们只需要最初建好点分树并排好序,每次只需要对每一个点为根的路径二分一下\(O(n\ logn)\),如此一来算法的时间复杂度瓶颈为\(O(n\ log^2n)\),本题得到完美解决。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Maxn=50005;
int n,m,h[Maxn],G[Maxn];
int rt,root,totsize,size[Maxn],mx[Maxn];
int lim,path[Maxn];ll ans;
vector<int>v[Maxn],g[Maxn],Ans;
bool vis[Maxn];
struct edge{int to,next,w;}e[Maxn*2],f[Maxn*2];
int getint(){
    int w=0,f=1;char ch=getchar();
    while(!isdigit(ch))ch!='-'?:f=-1,ch=getchar();
    while(isdigit(ch))w=w*10+ch-'0',ch=getchar();
    return w*f;
}
void addedge(int x,int y,int z){
    static int cnt;
    e[++cnt]=(edge){y,h[x],z};h[x]=cnt;
}
void AddEdge(int x,int y){
    static int cnt;
    f[++cnt]=(edge){y,G[x]};G[x]=cnt;
}
void getroot(int x,int fa){
    size[x]=1;mx[x]=0;
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y]||y==fa)continue;
        getroot(y,x);
        size[x]+=size[y];
        mx[x]=max(mx[x],size[y]);
    }
    mx[x]=max(mx[x],totsize-size[x]);
    if(mx[x]<mx[root])root=x;
}
void getsize(int x,int fa){
    totsize++;
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y]||y==fa)continue;
        getsize(y,x);
    }
}
void dfs(int x,int fa,int dis,vector<int>&v){
    v.push_back(dis);
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y]||y==fa)continue;
        dfs(y,x,dis+e[i].w,v);
    }
}
void Build(int x){
    vis[x]=1;v[x].push_back(0);
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y])continue;
        dfs(y,x,e[i].w,v[x]);
    }
    sort(v[x].begin(),v[x].end());
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y])continue;
        totsize=root=0;
        getsize(y,x);getroot(y,x);
        dfs(y,x,e[i].w,g[root]);
        sort(g[root].begin(),g[root].end());
        AddEdge(x,root);Build(root);
    }
}
ll calc(vector<int>&v){
    ll ret=0;
    for(int i=0;i<v.size();i++)ret+=v.end()-lower_bound(v.begin(),v.end(),lim-v[i]);
    return ret;
}
void solve(int x){
    ans+=calc(v[x]);
    for(int i=G[x];i;i=f[i].next){
        int y=f[i].to;
        ans-=calc(g[y]);
    }
    if(ans>=m)return;
    for(int i=G[x];i;i=f[i].next){
        int y=f[i].to;
        solve(y);
    }
}
bool Judge(int mid){
    lim=mid;ans=0;
    solve(rt);
    return ans>=m*2;
}
void dfs2(int x,int fa,int dis){
    path[++path[0]]=dis;
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y]||y==fa)continue;
        dfs2(y,x,dis+e[i].w);
    }
}
void Count(int x){
    path[0]=0;path[++path[0]]=0;
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y])continue;
        int now=path[0];
        dfs2(y,x,e[i].w);
        for(int j=now+1;j<=path[0];j++){
            int pos=lower_bound(path+1,path+now+1,lim-path[j])-path;
            for(int k=pos;k<=now;k++)Ans.push_back(path[j]+path[k]);
        }
        sort(path+now+1,path+path[0]+1);
        inplace_merge(path+1,path+now+1,path+path[0]+1);
    }
}
void solve2(int x){
    vis[x]=1;Count(x);
    for(int i=G[x];i;i=f[i].next){
        int y=f[i].to;
        solve2(y);
    }
}
int main(){
    n=getint();m=getint();
    for(int i=1;i<n;i++){
        int x=getint(),y=getint(),z=getint();
        addedge(x,y,z);addedge(y,x,z);
    }
    mx[0]=n+1;totsize=n;root=0;
    getroot(1,0);rt=root;
    Build(root);
    int l=0,r=5e8;
    while(l<=r){
        int mid=(l+r)>>1;
        if(Judge(mid))l=mid+1;
        else r=mid-1;
    }
    memset(vis,0,sizeof(vis));
    lim=l;ans=0;solve2(rt);
    while(Ans.size()<m)Ans.push_back(l-1);
    sort(Ans.begin(),Ans.end(),greater<int>());
    for(int i=0;i<m;i++)cout<<Ans[i]<<"\n";
}

转载于:https://www.cnblogs.com/Trrui/p/9686158.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值