BZOJ 3631 [JLOI2014]松鼠的新家

题意:给定一些树上的路径覆盖,求出每个点被覆盖了多少次。

 

拿到这题第一反应显然是树剖。。但实际上有更加方便的做法,那就是树上差分。

 

首先我们联想一下对数列进行差分的做法。序列差分这个技巧一般适用于:执行若干次区间加减,到最后再统计每个点的权值。设T是A的差分序列,我们把将a~b这个区间中的每个点加上c的操作(A[i]+=c  a<=i<=b)转变成对T的操作T[a]+=c,T[b+1]-=c,最后A[k]的值就是Sum{T[i]}(1<=i<=k)即T的一个前缀和。这个很容易能理解,随便在脑中yy一下或者画画图都能理解。

然后我们再来看下这道题。这道题与序列上有一定相似之处:执行若干次路径上的加减,最后统计每个点的权值。我们可以将在序列上进行差分的方法拓展到树上。对于s~t这个路径上每个点加上c的操作,我们可以对树A的“差分树”T进行操作:T[s]+=c,T[t]+=c,T[LCA(s,t)]-=c,T[Father[LCA(s,t)]]-=c。即给s这个点加上c,给t这个店加上c,给s和t的最近公共祖先和这个祖先的父亲减去c。最后每个点的权值就是这个点的子树权值之和。

这样做为什么是正确的呢?我们来yy一下一般情况:对于一棵树,从s走到t可以这么走:先从s走到LCA(s,t),再从t走到LCA(s,t)。为了更方便接下来的叙述,我们可以想象有两个人分别从s和t走到LCA(s,t)。然后,我们要将路径s~t上每个点加上c,因为我们最后统计的是每个点的子树权值之和,所以说白了就是这个标记要向它的父亲上传;所以当我们给s和t都加上c后,上传到LCA(s,t)的时候就变成了2c,显然在这里是要减去c的;而对于LCA(s,t)的父亲,由于LCA(s,t)的标记也要上传,所以在LCA(s,t)的父亲处再减去一个c。这段话比较长,但是只要深刻理解数列差分的技巧,理解树上差分也是很容易的。

 

最后说几个实现上的细节。

第一,对于LCA(s,t),如果它是根,那么就没有将它父亲减去c的操作了。

第二,最后子树统计的时候的方法比较多。可以用类似拓扑排序的思想,从最后一层结点“逐层上传”。也可以用树型dp。这个随个人喜好而定。

 

具体看代码~

 

#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const size_t Max_N(300050);
const size_t Max_M(600050);
const int root(1);

void Get_Val(int &Ret)
{
    Ret = 0;
    char ch;
    while ((ch = getchar()), (ch > '9' || ch < '0'))
        ;
    do
    {
        (Ret *= 10) += ch - '0';
    }
    while ((ch = getchar()), (ch >= '0' && ch <= '9'));
}

int N;
int A[Max_N];
int Father[Max_N], Deep[Max_N];
vector<int> Son[Max_N];
int Anc[Max_N][35];

int Total;
int Head[Max_N];
int To[Max_M], Next[Max_M];

int Value[Max_N];

inline
void Add_Edge(const int &s, const int &t)
{
    ++Total;
    To[Total] = t;
    Next[Total] = Head[s], Head[s] = Total;
}

void init()
{
    int x, y;
    Get_Val(N);
    for (int i = 1;i <= N;++i)
        Get_Val(A[i]);
    for (int i = 1;i != N;++i)
    {
        Get_Val(x), Get_Val(y);
        Add_Edge(x, y), Add_Edge(y, x);
    }
}

void make_tree(const int &u, const int &fa)
{
    int v;
    for (int i = Head[u];i;i = Next[i])
    {
        v = To[i];
        if (v != fa)
        {
            Deep[v] = Deep[u] + 1;
            Son[u].push_back(v);
            Father[v] = u;
            make_tree(v, u);
        }
    }
}

void preprocess()
{
    memset(Anc, -1, sizeof(Anc));
    for (int i = 1;i <= N;++i)
        Anc[i][0] = Father[i];
    for (int j = 1;(1 << j) <= N;++j)
        for (int i = 1;i <= N;++i)
            Anc[i][j] = Anc[Anc[i][j - 1]][j - 1];
}

int LCA(int p, int q)
{
    if (Deep[p] < Deep[q])
        swap(p, q);
    int log;
    for (log = 1;(1 << log) <= Deep[p];++log); --log;
    for (int i = log;i >= 0;--i)
        if (Deep[p] - (1 << i) >= Deep[q])
            p = Anc[p][i];
    if (p == q)
        return p;
    for (int i = log;i >= 0;--i)
        if (Anc[p][i] != -1 && Anc[p][i] != Anc[q][i])
            p = Anc[p][i], q = Anc[q][i];
    return Father[p];
}

void Add(const int &s, const int &t, const int &w)
{
    int lca = LCA(s, t);
    Value[s] += w, Value[t] += w, Value[lca] -= w;
    if (lca != root)
        Value[Father[lca]] -= w;
}

void SonTree_Sum(const int &u)
{
    for (vector<int>::size_type i = 0;i != Son[u].size();++i)
    {
        SonTree_Sum(Son[u][i]);
        Value[u] += Value[Son[u][i]];
    }
}

void work()
{
    Deep[root] = 1, make_tree(root, Father[root] = -1);
    preprocess();
    for (int i = 2;i <= N;++i)
    {
        Add(A[i - 1], A[i], 1);
        Add(A[i], A[i], -1);
    }
    SonTree_Sum(root);
    for (int i = 1;i <= N;++i)
        printf("%d\n", Value[i]);
}

int main()
{
    init();
    work();
    return 0;
}
BZOJ 3631

 

你可能感兴趣的:(BZOJ 3631 [JLOI2014]松鼠的新家)