[BZOJ3784]树上的路径 点分治序+ST表

本文介绍了如何利用点分治序和ST表来解决树上路径的问题。点分治序的长度为nlogn,通过在出现节点u的分治结构中找到节点v所在的区间,可以采用类似NOI2010超级钢琴的维护策略,通过堆进行操作,复杂度达到O(nlog^2n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点分治序就是每次点分治时的dfs序拼起来。点分治序的长度应该是nlogn的。
然后对于每一条路径(u,v),在出现了u的分治结构中,v总是在点分治序的一段区间里,就可以同NOI2010超级钢琴一样维护了,就是从堆中取出最大值,并把所在区间再分成两部分扔进堆里,重复m次即可。复杂度O(nlog^2n)。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int maxn=50010;
const int mlgn=900010;
int n,m,tim,a[mlgn],fz[maxn],ed[maxn],sz[maxn],st[mlgn][20],lg[mlgn];
bool vis[maxn];
struct edge
{
    int t,w;
    edge *next;
}*con[maxn];
struct node
{
    int p,l,r,w,d;
}t[mlgn];
inline bool operator <(const node &p,const node &q){return p.w<q.w;}
priority_queue <node> Q;
inline int read()
{
    char ch=getchar();int x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline int amx(int p,int q)
{
    return (a[p]<a[q])?q:p;
}
void ins(int x,int y,int w)
{
    edge *p=new edge;
    p->t=y;
    p->w=w;
    p->next=con[x];
    con[x]=p;
}
void getroot(int v,int fa,int size,int &root)
{
    bool flag=1;
    sz[v]=1;
    for(edge *p=con[v];p;p=p->next)
        if(!vis[p->t]&&p->t!=fa)
        {
            getroot(p->t,v,size,root);
            sz[v]+=sz[p->t];
            if(sz[p->t]*2>size) flag=0;
        }
    if(size>2*sz[v]) flag=0;
    if(flag) root=v;    
}
void dfs(int v,int fa,int d)
{
    fz[v]=++tim;
    a[tim]=d;
    for(edge *p=con[v];p;p=p->next)
        if(!vis[p->t]&&p->t!=fa) dfs(p->t,v,d+p->w);
    ed[v]=tim;  
}
void solve(int v,int size)
{
    int rt;
    getroot(v,-1,size,rt);
    vis[rt]=1;
    dfs(rt,-1,0);
    for(edge *p=con[rt];p;p=p->next)
        if(!vis[p->t]) 
            for(int i=fz[p->t];i<=ed[p->t];i++)
                t[i].p=i,t[i].l=fz[rt],t[i].r=fz[p->t]-1;
    for(edge *p=con[rt];p;p=p->next)
        if(!vis[p->t]) solve(p->t,(sz[p->t]>sz[rt]?size-sz[rt]:sz[p->t]));       
}
void init()
{
    lg[0]=-1;
    for(int i=1;i<=tim;i++)
        lg[i]=lg[i>>1]+1; 
    for(int i=1;i<=tim;i++)
        st[i][0]=i;
    for(int k=1;(1<<k)<=tim;k++)
        for(int i=1;i<=tim;i++)
            if(i+(1<<(k-1))>tim) st[i][k]=st[i][k-1];
            else st[i][k]=amx(st[i+(1<<(k-1))][k-1],st[i][k-1]);
}
inline int qry(int l,int r)
{
    int k=lg[r-l+1];
    return amx(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
    n=read();m=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read(),w=read();
        ins(x,y,w);
        ins(y,x,w);
    }
    solve(1,n); 
    init();
    for(int i=1;i<=tim;i++)
    {
        t[i].d=qry(t[i].l,t[i].r);
        t[i].w=a[t[i].d]+a[t[i].p];
        Q.push(t[i]);
    }
    while(m--)
    {
        node r=Q.top(),s=r,u=r;Q.pop();
        printf("%d\n",r.w);
        if(r.d>r.l) 
        {
            s.r=r.d-1;
            s.d=qry(s.l,s.r);
            s.w=a[s.d]+a[s.p];
            Q.push(s);
        }
        if(r.d<r.r)
        {
            u.l=r.d+1;
            u.d=qry(u.l,u.r);
            u.w=a[u.d]+a[u.p];
            Q.push(u);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值