BZOJ 3784: 树上的路径(点分治+ST表+堆堆堆)

题目喵述

权限门

题目大意:给一棵树,有N个点,有边权。所有无向路径中,请你输出前M大的。

N<=50000
M<=Min(300000,n*(n-1) /2)


思路

树上超级钢琴。(拖延症的结果就是现在才写这题)

学了一种点分治的套路,在做点分治的时候求出整棵树的dfs序。可以发现,每个点在dfs序列中出现不超过log次。我们不妨称其为点分治序。

然后类比超级钢琴,将序列转换到树上。强制路径过分治中心,然后每一条路径的长度对应Dis[x]+Dis[y]。其中x和y要求在分治中心的不同子树内。这里我们不可以直接做然后减什么的,有一种方便的技巧就是枚举儿子,每个儿子dfs一次。记录每个点为路径尾能取到的头的区间[L,R]。

接下来,直接在点分治序上做ST表,丢进大根堆里然后分裂区间即可,这里跟在序列上几乎一样。

时间复杂度是两个log的。

还有一种二分的做法,加上每次点分治总共有3个log,是过不了的,但是先点分治一次,排好序并记下来,然后就只有两个log了,也是很强的。


代码

#include 
#define maxn 800010
#define Lg 20

using namespace std;

int n, m, cur = -1;
struct List{
    List *next;
    int obj, len;
}*head[maxn], Edg[maxn<<1];

void Addedge(int a, int b, int c){
    Edg[++cur].next = head[a];
    Edg[cur].obj = b;
    Edg[cur].len = c;
    head[a] = Edg+cur;
}

bool Vis[maxn];
int Root, Sum, Dfn;
int son[maxn], siz[maxn], fa[maxn];
int T[maxn], f[Lg][maxn], g[Lg][maxn];
int tL[maxn], tR[maxn], Dis[maxn];

struct World{
    int x, y, l, r;
    World() {}
    World(int _x, int _y, int _l, int _r){
        x = _x;  y = _y;  l = _l;  r = _r;
    }
    bool operator < (const World& OTHER) const{
        return Dis[x] + Dis[y] < Dis[OTHER.x] + Dis[OTHER.y];
    }
};

priority_queue  Q;

void GetRoot(int x, int ff){
    siz[x] = 1;  son[x] = 0;
    for(List *p = head[x]; p; p = p->next){
        int v = p->obj;
        if(v == ff || Vis[v])  continue;
        GetRoot(v, x);
        siz[x] += siz[v];
        son[x] = max(son[x], siz[v]);
        fa[v] = x;
    }
    son[x] = max(son[x], Sum-siz[x]);
    if(son[x] < son[Root])  Root = x;
}

void Dfs(int x, int ff, int L, int R, int dep){
    tL[++Dfn] = L;  tR[Dfn] = R;  Dis[Dfn] = dep;
    for(List *p = head[x]; p; p = p->next){
        int v = p->obj, l = p->len;
        if(v == ff || Vis[v])  continue;
        Dfs(v, x, L, R, dep+l);
    }
}

void Solve(int x){
    Vis[x] = true;
    siz[fa[x]] = Sum - siz[x];

    Dfn ++;
    tL[Dfn] = Dfn;  tR[Dfn] = Dfn;  Dis[Dfn] = 0;
    int res = Dfn;
    for(List *p = head[x]; p; p = p->next){
        int v = p->obj, l = p->len;
        if(Vis[v])  continue;
        Dfs(v, 0, res, Dfn, l);
    }

    for(List *p = head[x]; p; p = p->next){
        int v = p->obj;
        if(Vis[v])  continue;
        Root = 0;
        Sum = siz[v];
        GetRoot(v, 0);
        Solve(Root);
    }
}

void Pre(){

    T[1] = 0;
    for(int i = 2; i <= Dfn; i++)  T[i] = T[i>>1] + 1;

    for(int i = 1; i <= Dfn; i++)  f[0][i] = Dis[i], g[0][i] = i;

    for(int i = 1; i <= T[Dfn]; i++)
        for(int j = 1; j <= Dfn-(1<1; j++){
            if(f[i-1][j] > f[i-1][j+(1<<(i-1))]){
                f[i][j] = f[i-1][j];
                g[i][j] = g[i-1][j];
            }
            else{
                f[i][j] = f[i-1][j+(1<<(i-1))];
                g[i][j] = g[i-1][j+(1<<(i-1))];
            }
        }
}

void Push(int x, int l, int r){
    if(l > r)  return;
    int t = T[r-l+1], pos;
    if(f[t][l] > f[t][r-(1<1])  pos = g[t][l];
    else  pos = g[t][r-(1<1];
    Q.push(World(x, pos, l, r));
}

int main(){

    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; i++)  head[i] = NULL;

    int a, b, c;
    for(int i = 1; i < n; i++){
        scanf("%d%d%d", &a, &b, &c);
        Addedge(a, b, c);
        Addedge(b, a, c);
    }

    Root = 0;
    son[0] = Sum = n;

    GetRoot(1, 0);
    Solve(Root);

    Pre();

    for(int i = 1; i <= Dfn; i++)  Push(i, tL[i], tR[i]);

    for(int i = 1; i <= m; i++){
        World tmp = Q.top();  Q.pop();
        printf("%d\n", Dis[tmp.x] + Dis[tmp.y]);
        Push(tmp.x, tmp.l, tmp.y-1);
        Push(tmp.x, tmp.y+1, tmp.r);
    }

    return 0;
} 

你可能感兴趣的:(BZOJ,点分治,倍增,STL)