码迷,mamicode.com
首页 > 其他好文 > 详细

虚树复习

时间:2020-03-14 01:21:11      阅读:51      评论:0      收藏:0      [点我收藏+]

标签:世界   sign   一个   pen   printf   back   树形dp   --   turn   

我记得我学过虚树啊 除了做过的题目有提交的痕迹 脑子空空如也。

今天一定要复习好虚树 我没剩多少时间了。

1.虚树是干嘛的?

对于一道题目 我们发现其每次询问树上的一些点集的某种定义下的答案 通常我们不需要再次遍历整棵树来寻找答案 可以利用题目中给出的这些点集建立一颗不存在的树 即称虚树。

2.如何构建
我们可以发现 如果单纯的把点插到一颗不存在的树上很好插入 但是我们不一定能维护好类似于原本树的形态。所以这个时候我们需要借助两两点之间的LCA来帮忙构建这棵树 维护好这棵树的基本形态。

3.复杂度的保证:由于是询问中的点集 我们可以将其按原本的dfs序排序 然后按顺序插入到我们的虚树当中去 并两两点求一波LCA
可以发现 LCA可以O(1)也可以logn 由于时间复杂度的瓶颈在于排序 所以总复杂度为点集大小K*logn.

4.应用
大多数题目都是让我们构建出虚树然后在虚树上搞点事情 如树形dp 点分治 线段树优化建图什么的。
总之这个技巧还是挺重要的。

虚树构建的过程:

虚树的构建依赖于单调栈,我们利用单调栈来维护 根到某个节点的一条笔直的链 这样我们就可以把整个基础的树的形态给完整的演绎出来。

想着 单调栈维护的是什么东西+画图思考 就可以得到一个虚树的构建过程了 一般这样的话就不容易写错了。
为什么要在弹栈的时候建边? 由于有些点深度小但是是后来以LCA的形式加入的 如果在某个点一开始就加入那么显然后面来一个LCA的话建边会错乱。

由于两两点之间的LCA只有一个 所以我们构建的虚树的空间复杂度也是询问点点集的复杂度。
code:

inline void insert(int x)
{
    if(s[top]==1){s[++top]=x;return;}
    int lca=LCA(s[top],x);
    if(s[top]==lca){s[++top]=x;return;}
    while(top>1&&dfn[s[top-1]]>=dfn[lca])
    {
        ADD(s[top-1],s[top]);
        --top;
    }
    if(s[top]!=lca)
    {
        ADD(lca,s[top]);
        s[top]=lca;
    }
    s[++top]=x;
}
int main()
{
    sort(w+1,w+1+n,cmp);
    s[top=1]=1;
    rep(1,n,i)if(w[i]!=1)insert(w[i]);
    while(top>1)ADD(s[top-1],s[top]),--top;
}

值得注意的是建的边可以是单向的 如果双向边没啥大用的话...

LINK:luogu2495SDOI2011消耗战
这道题目 初看是一道比较裸的树形dp 但是每次给出了关键点了 所以我们直接构建虚树在虚树上dp即可。
值得一提的是我们很容易发现 两个点同时为关键点且拥有祖先关系 那么 其中的一个点可以不要 因为祖先是一定要被割断的 割断的同时儿子也一定被隔断了...

所以我们在构建虚树的时候 可以把s[top]==lca时 不把x加入栈中 因为s[top]此时一定是关键点 且s[top]还是x的祖先。

估计是边权范围假了 INF 1e9 wa了

const int MAXN=250010;
int n,m,k,len,T,cnt,top;
int fa[MAXN][20],d[MAXN],dfn[MAXN],s[MAXN],Log[MAXN],a[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1];
ll f[MAXN],mx[MAXN],e[MAXN<<1];vector<int>g[MAXN];
inline int cmp(int a,int b){return dfn[a]<dfn[b];}
inline void add(int x,int y,int z)
{
    ver[++len]=y;
    nex[len]=lin[x];
    lin[x]=len;
    e[len]=z;
}
inline void dfs(int x,int father)
{
    d[x]=d[father]+1;
    fa[x][0]=father;dfn[x]=++cnt;
    rep(1,Log[d[x]],i)fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=lin[x];i;i=nex[i])
    {
        int tn=ver[i];
        if(tn==father)continue;
        mx[tn]=min(mx[x],e[i]);
        dfs(tn,x);
    }
}
inline void add(int x,int y){g[x].push_back(y);}
inline int LCA(int x,int y)
{
    if(d[x]<d[y])swap(x,y);
    for(int i=Log[d[x]];i>=0;--i)
        if(d[fa[x][i]]>=d[y])x=fa[x][i];
    if(x==y)return x;
    for(int i=Log[d[x]];i>=0;--i)
        if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
inline void insert(int x)
{
    if(top==1){s[++top]=x;return;}
    int lca=LCA(s[top],x);
    if(lca==s[top])return;
    while(top>1&&dfn[s[top-1]]>=dfn[lca])
    {
        add(s[top-1],s[top]);
        --top;
    }
    if(s[top]!=lca)
    {
        add(lca,s[top]);
        s[top]=lca;
    }
    s[++top]=x;
}
inline void dp(int x)
{
    f[x]=mx[x];
    if(!g[x].size())return;
    ll sum=0;
    for(unsigned int i=0;i<g[x].size();++i)
    {
        int tn=g[x][i];
        dp(tn);
        sum+=f[tn];
    }
    f[x]=min(f[x],sum);
    g[x].clear();
}
int main()
{
    freopen("1.in","r",stdin);
    get(n);
    rep(2,n,i)
    {
        int x,y,z;
        get(x);get(y);get(z);
        add(x,y,z);add(y,x,z);
        Log[i]=Log[i>>1]+1;
    }
    mx[1]=INF;dfs(1,0);
    get(m);
    rep(1,m,i)
    {
        int k;get(k);s[top=1]=1;
        rep(1,k,j)get(a[j]);
        sort(a+1,a+1+k,cmp);
        rep(1,k,j)insert(a[j]);
        while(top>1)add(s[top-1],s[top]),--top;
        dp(1);printf("%lld\n",f[1]);
    }
    return 0;
}

这道虚树的模板题写起来挺舒服的。。明天一定要把世界树给肝出来!

虚树复习

标签:世界   sign   一个   pen   printf   back   树形dp   --   turn   

原文地址:https://www.cnblogs.com/chdy/p/12489914.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!