【HNOI2014】世界樹(worldtree) (虛樹詳解)


題目大意

原題又臭又長,總之簡介來說的話就是
給定一棵樹,有若干個詢問,每次給定m個點,每個點都被這m個點中最近(距離相同,編號小的近)的點管轄。問m個點分別管幾個點。
n<=300000,q<=300000,∑m<=300000。

The Solution

我們先來對題目進行分析,很明顯,我們要往樹上來想,那么怎么想呢?第一想法肯定是暴力啊!!可是,給定m個點,詢問它分別管轄幾個點,會有很多中狀態,且都是可變的,這TM怎么暴力?
或許可能可以YY到處理它lca及自己的狀態,然后再在樹上貪心之類的~囧~
好主意估計能行,水點分吧,可這也很GG啊

好吧口胡了這么久,接下來切入主題

正解是虛樹樹形DP

那么問題來了

什么是虛樹呢?

(這里引用了別人的一些資料與圖,供大家學習。)

假設如下的一棵樹

這里寫圖片描述

藍色的是詢問點。紅色點就會在虛樹上。

這里寫圖片描述

我們用一個棧來維護虛樹的“當前這一坨東西”…例如我們在棧中加入了18。然后接下來打算加入一個16。

我們現在發現這個棧頂的lca,也就是2,是有用的,那么我們現在就要把18彈掉,換成2,然后再扔進去一個16。

接下來我們要加一個20,那么它與棧頂的lca為2。我們就考慮16,16是沒用的,把它彈掉,然后看見2,正好就是lca,就保留。

類似這樣我們可以發現開始把所有詢問點加入虛樹后,我們把詢問點按dfs序排個序,這時棧里面應該維護一個奇怪的玩意,先計算一個新加的點與棧頂的lca,然后如果一個棧里的東西一直都“沒用”,也就是深度比這個lca來得大,就一直彈出棧頂,最后如果棧頂不是lca,就把lca加入棧,並且加入虛樹,然后再加入這個點。

這樣我們就可以求出虛樹啦,同時我們也可以得到虛樹上每一個點的父親節點。

接下來我們考慮如何求出虛樹上每個點被哪個點控制。我們可以用一個 pair<int,int> 來存它到控制點的距離和控制點,然后我們用每一個點更新它的父親,再用每一個父親更新它的孩子。注意到因為所有虛樹上的點的lca也在虛樹上,所以對於虛樹上某一個點和某一個控制點,第一遍必然會更新到lca,第二遍必然會更新到這個點,所以這個做法是正確的。

然后我們要考慮虛樹以外的點要怎么維護。

比如對於這樣一個小樹,紅色的點在虛樹上。

這里寫圖片描述

我們考慮虛樹上的一條父子邊,對應實際的樹上就會是一坨東西。比如這棵樹上2-5就對應4和6。

具體地說,對於一條父子邊(f,x),f=fa[x],那么對應實際樹上的就是f在x方向的這棵子樹除掉x這棵子樹。

例如2-5就表示4這棵子樹除掉5這棵子樹,就是4和6這兩個點。

我們發現樹上除了這些父子邊,還有一些沒有被計入統計的點,需要分別考慮,虛樹根往上的都需要單獨統計,例如1,還有像圖中的3這樣也不會被統計到。

注意到跟這些點最近的點必然和和這些點相連的點最近的是同一個點,比如1和3最近的都與2最近的一樣。

現在,如果5與2最近的是同一個點,那么4和6必然也是這一個點。

否則我們可以發現,這是與深度有關的。比如2最近的點->2距離為p,5最近的點->5距離為q。

那么與控制5的點距離不超過(p+q+2到5距離)/2的點都應該選擇5最近的點,那么深度就要>=dep[5]-(near_dis(2)+near_dis(5)+dis(2,5))/2+near_dis(5)。

只要從5開始往上跳到這個深度,計算一下就可以了。

需要注意的是,如果(p+q+2到5距離)是偶數的話,一定會有一個(些)點到2和5最近的點距離相同,那么這個(些)點應該選編號小的一個。即因為正常情況下這些點會選擇下面那個點的控制點,所以如果上面那個點控制點編號小,就要考慮深度++。

所以基本的思路都清楚了,我們只要寫一個倍增維護一下lca和往上跳這種東西就可以了。復雜度大概是O((n+q+∑m)logn)的。

參考CODE

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)
#define INF 1 << 30
#define N 600005 

using namespace std;

int read(int &n)
{
    char ch = ' ';
    int q = 0, w = 1;
    for (;(ch != '-') && ((ch < '0') || (ch> '9'));ch = getchar());
    if (ch == '-') w = -1,ch = getchar();
    for (; ch >= '0' && ch <= '9';ch = getchar()) q = q * 10 + ch - 48;
    n = q * w;
    return n;
}

namespace ib {char b[100];}

inline void pint(int x)
{
    if (x == 0)
    {
        putchar(48);
        return;
    }
    if (x < 0) 
    {
        putchar('-');
        x = -x;
    }
    char *s = ib :: b;
    while (x) *(++ s) = x % 10,x /= 10;
    while (s != ib :: b) putchar((* (s --)) + 48);
}

typedef long long ll;

struct Edge
{
    int to,next;
    Edge(void){}
    Edge(int a,int b) : to(a),next(b){}
}E[N];

int tot = 0,n,Q;
int Final[N];

void Link(int x,int y)
{
    E[++ tot] = Edge(y,Final[x]),Final[x] = tot;
    E[++ tot] = Edge(x,Final[y]),Final[y] = tot; 
}

int Dad[N],Up[N][20],Dep[N],M=0,Dfsn[N],C=0,Son[N];

void Dfs_rt(int x)
{
    Dfsn[x] = ++ C;
    Son[x] = 1;
    for (int k = Final[x];k;k = E[k].next)
    {
        if (E[k].to == Dad[x]) continue;
        Dad[E[k].to] = Up[E[k].to][0] = x;
        Dep[E[k].to] = Dep[x] + 1;
        Dfs_rt(E[k].to);
        Son[x] += Son[E[k].to];
    }
}

void Build_rt()
{
    Dfs_rt(1);
    fo(g,1,19)
        fo(i,1,n)
            if (Up[i][g - 1]) Up[i][g] = Up[Up[i][g - 1]][g - 1];
}
////jump up (x=fa[x]) until dep[x]=d
int Jump(int x,int d)
{
    fd(i,19,0)
        if (!Up[x][i] || Dep[Up[x][i]] < d);else x = Up[x][i];
    return x;
}

int Lca(int x,int y)
{
    if (Dep[x] > Dep[y]) swap(x,y);
    y = Jump(y,Dep[x]);
    if (x == y) return x;
    fd(i,19,0)
        if (Up[x][i] != Up[y][i]) x = Up[x][i],y = Up[y][i];
    return Dad[x];
}

typedef pair<int,int> P;
bool cmp_Dfsn(int a,int b) { return Dfsn[a] < Dfsn[b];}
int ss[N],vs[N],st[N],vfa[N],emp[N],vfe[N],ans[N],ss_[N],vn,stn = 0,sn;
P Dot[N]; //(dis,controller)
//vs: points in vtree 
void Build_vt()
{
    vn = stn = 0;
    fo(i,1,sn) ss_[i] = ss[i];
    sort(ss + 1,ss + 1 + sn,cmp_Dfsn);
    fo(i,1,sn) 
    {
        vs[++ vn] = ss[i];
        Dot[ss[i]] = P(0,ss[i]);
        ans[ss[i]] = 0;
    }
    fo(i,1,sn)
    {
        int x = ss[i];
        if (! stn) 
        {
            st[++ stn] = x;
            vfa[x] = 0;
            continue;
        }
        int lca = Lca(x,st[stn]);
        for (;Dep[st[stn]] > Dep[lca];stn --)
            if (Dep[st[stn - 1]] <= Dep[lca]) vfa[st[stn]] = lca;
        if (st[stn] != lca)
        {
            vs[++ vn] = lca;
            Dot[lca] = P(INF,0);
            vfa[lca] = st[stn];
            st[++ stn] = lca;
        }
        vfa[x] = lca;
        st[++ stn] = x;
    }
    sort(vs + 1,vs + 1 + vn,cmp_Dfsn);//注意到按dfs序排序是滿足兒子一定在父親的后面的 
    fo(i,1,vn)
    {
        int x = vs[i];
        emp[x] = Son[x];
        if (i > 1) vfe[x] = Dep[x] - Dep[vfa[x]];
    }
    fd(i,vn,2)
    {
        int x = vs[i],
            f = vfa[x];
        Dot[f] = min(P(Dot[x].first + vfe[x],Dot[x].second),Dot[f]);
    }
    fo(i,2,vn)
    {
        int x = vs[i],
            f = vfa[x];
        Dot[x] = min(P(Dot[f].first + vfe[x],Dot[f].second),Dot[x]);
    }
    fo(i,1,vn)
    {
        int x = vs[i],
            f = vfa[x];
           //樹根往上的點 
        if (i == 1) 
        {
            ans[Dot[x].second] += n - Son[x];
            continue;
        } 
        int fp = Jump(x,Dep[f] + 1),
            cnt = Son[fp] - Son[x];
        emp[f] -= Son[fp]; //這一棵子樹已經處理過了
        if (Dot[f].second == Dot[x].second)
        {
            ans[Dot[x].second] += cnt;
            continue;
        }
        int mid = Dep[x] - (Dot[f].first + Dot[x].first + vfe[x]) / 2 + Dot[x].first;
        if ((Dot[f].first + Dot[x].first + vfe[x]) % 2 == 0 && Dot[f].second < Dot[x].second) mid ++;
        int tmp = Son[Jump(x,mid)] - Son[x];
        ans[Dot[x].second] += tmp;
        ans[Dot[f].second] += cnt - tmp;
    }
    fo(i,1,vn) ans[Dot[vs[i]].second] += emp[vs[i]];
    fo(i,1,sn)
    {
        pint(ans[ss_[i]]);
        putchar(' ');
    }
    putchar(10);
}
int main()
{
    freopen("worldtree.in","r",stdin);
    freopen("worldtree.out","w",stdout);
    read(n); 
    fo(i,1,n - 1)
    {
        int x,y;
        read(x),read(y);
        Link(x,y);
    }
    Build_rt();
    read(Q);
    while (Q --)
    {
        read(sn);
        fo(i,1,sn) read(ss[i]);
        Build_vt(); 
    }
    return 0;
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com