[虚树] 学习笔记

水平不够,学习来凑
又开了个天大的新坑…

例题:

SDOI 2011 消耗战

树形DP:

题目大意就是讲:给出一棵树,有边权,然后给出K个查询点,问从1号店不能到任何一个查询点的代价是多少.
先考虑一下树形动归.
dp[i]表示从1不能到以i为根的子树中的所有查询点的最小代价
考虑维护一个量,mins[i]表示从1到i路径最小权值
dp[i]显然要通过枚举子树转移
如果i不是叶子的话,那么 dp[i]=min(jdp[j],mins[j]) d p [ i ] = m i n ( ∑ j d p [ j ] , m i n s [ j ] ) j为i的子树邻接点
显然每次得到dp[1]是要花费O(n)的代价的 只有40pts…

虚树优化:

如果是上面的树形DP,那么总的复杂度就是 O(n*m)
然而题目里告诉我们 mki<=500000 ∑ k i m <= 500000 ,就要考虑在这下手了
如果我们可以发现,在转移的时候很多点都是无用的,其实有用的点就只有查询点和一些他们的LCA
那么我们就可以考虑重新构造出一棵树,每个节点的维护值与原树等价,但是深度很低,我们把这个树称为虚树。
虚树上的点一定是<=2*k的
这样这题就可做了

构造虚树:

首先我们需要一个单调栈来维护一条从根节点延伸下来的链,然后我们有四个节点。

1:当前点

2:栈顶

3:次栈顶

4:lca(当前点,栈顶)

考虑点4,有两种可能。

1:点4在点3的上方,那么点3向点2俩边,点2出栈。

2:点4在点2和点3的中间,那么点4向点2连边,点2出栈,点4入栈,链底部显然是点1,所以点1入栈。

于是我们就构造出了一颗虚树。
这里写图片描述
假设查询点为 5、8、10,那么虚树大概是长这个样子
这里写图片描述

实现的一些小细节:

1:求LCA树剖不知道比倍增快到哪去了
2:我们对于每次的查询重新构造时,并不需要每次都初始化head数组,而是可以在上次的树遍历里直接初始化(要不T成SB

例题代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long
const int maxm=251000; 
int head[maxm],to[maxm<<1],net[maxm<<1],cost[maxm<<1];
int son[maxm],siz[maxm],fa[maxm],top[maxm],dfn[maxm],deep[maxm],mins[maxm];
int h[maxm],stk[maxm],tops;
int dp[maxm];
bool vis[maxm];
int tot,cnt;
int n,q;
inline void addedge(int u,int v,int c)
{
    if(u==v) return;
    cnt++;
    to[cnt]=v,cost[cnt]=c,net[cnt]=head[u],head[u]=cnt;
}
inline bool comp(int a,int b)
{
    return dfn[a]<dfn[b];
}
void dfs1(int now,int fax,int dep)
{
    dfn[now]=++tot,deep[now]=dep,fa[now]=fax,siz[now]=1;
    for(int i=head[now];i;i=net[i])
    if(to[i]!=fax)
    {
        mins[to[i]]=std::min(mins[now],cost[i]);
        dfs1(to[i],now,dep+1);

        siz[now]+=siz[to[i]];
        if(siz[to[i]]>siz[son[now]]) son[now]=to[i]; 
    }
}
void dfs2(int now,int topx)
{
    top[now]=topx;
    vis[now]=1;
    if(son[now]) dfs2(son[now],topx);
    for(int i=head[now];i;i=net[i])
    if(!vis[to[i]])
     dfs2(to[i],to[i]);
}
inline int LCA(int u,int v)
{
    while(top[u]!=top[v])
    {   
        if(deep[top[u]]<deep[top[v]]) std::swap(u,v);
        u=fa[top[u]];
    }
    return deep[u]<deep[v]?u:v;
}
void DFS(int now)
{
    //printf("-%d-\n",now);
    int ans=0;
    for(int i=head[now];i;i=net[i])
    {
        DFS(to[i]);
        ans+=dp[to[i]];
    }
    head[now]=0;
    dp[now]=ans?std::min(ans,mins[now]):mins[now];
}
void buildtree()
{
    cnt=0;
    int k;
    scanf("%lld",&k);
    for(int i=1;i<=k;i++)
     scanf("%lld",&h[i]);
    std::sort(h+1,h+k+1,comp);
    int num=1;
    for(int i=2;i<=k;i++)
     if(LCA(h[num],h[i])!=h[num]) h[++num]=h[i];
    stk[tops=1]=1;
    for(int i=1;i<=num;i++)
    {
        int now=h[i],lca=LCA(now,stk[tops]);  
        while(1)
        {  
            if(deep[lca]>=deep[stk[tops-1]])
            {  
                addedge(lca,stk[tops--],0);  
                if(lca!=stk[tops])stk[++tops]=lca;  
                break;  
            }  
            addedge(stk[tops-1],stk[tops],0);
            tops--;  
        }  
        if(stk[tops]!=now)stk[++tops]=now;  
    } 
    while(--tops) addedge(stk[tops],stk[tops+1],0);

    DFS(1);
    printf("%lld\n",dp[1]);
}
main()
{
    scanf("%lld",&n);
    for(int i=1;i<n;i++)
    {
        int u,v,c;
        scanf("%lld%lld%lld",&u,&v,&c);
        addedge(u,v,c),addedge(v,u,c);
    }
    mins[1]=1e60;
    dfs1(1,0,0),dfs2(1,1);
    //for(int i=1;i<=n;i++) printf("%d\n",mins[i]);
    memset(head,0,sizeof(head));

    scanf("%lld",&q);
    while(q--)
     buildtree();
    return 0;
}

题目集锦:

暂无…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值