• 虚树学习笔记


    这个知识点不是很难,其实就是一类特殊的树形dp:这类dp会有多次询问,每次询问给出一个点集合S然后这次询问的内容只与集合S的点相关。

    学习博客:https://blog.csdn.net/ouqingliang/article/details/81669281

    https://blog.csdn.net/zhouyuheng2003/article/details/79110326

    假如树的大小为n,询问个数问m,如果对于每次询问都在整棵树做树形dp,那么时间复杂度是O(n*m),TLE。但是我们注意到  所有询问的点 总数比较小。于是我们想其实每次dp不需要把整棵树dp,把S集合点建立一棵虚树然后在这棵虚树上dp即可。

    所以这个知识点的重点就是怎么求集合S的虚树?把点集按dfn排序,用一个栈表示虚树上从根到当前点的路径,然后通过比较栈元素的dfn和当前点x的dfn来建立虚树。上面两位大佬博客已经把过程说得很清楚了,画图仔细想一下就能明白。

    题目练习:

    这个算法重点是怎么建立虚树,具体求解的dp过程还是具体分析。

    洛谷P3320

    发现对于一次询问代价其实就是dist(a1,a2)+dist(a2,a3)+...dist(an,a1) (ai要按dfn排序)。问题是这道题的点是动态添加删除的,怎么办呢?发现其实添加一个点x,x的dfn前缀是l,x的dfn后缀是r,对答案的新增贡献就是dist(l,x)+dist(x,r)-dist(l,r)。删除同理。那么我们就可以用set维护当前点集,然后通过LCA快速算新增贡献即可。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int N=1e5+10;
    int n,m,t;
    set<int> S;
    set<int>::iterator it;
    
    int cnt=1,head[N],nxt[N<<1],to[N<<1],len[N<<1];
    void add_edge(int x,int y,int z) {
        nxt[++cnt]=head[x]; to[cnt]=y; len[cnt]=z; head[x]=cnt;
    }
    
    int num=0,dfn[N],idx[N],dep[N],f[N][20]; LL dis[N];
    void dfs(int x,int fa) {
        dfn[x]=++num; idx[num]=x; dep[x]=dep[fa]+1;
        for (int i=head[x];i;i=nxt[i]) {
            int y=to[i];
            if (!dfn[y]) {
                dis[y]=dis[x]+len[i];
                f[y][0]=x;
                for (int j=1;j<=t;j++) f[y][j]=f[f[y][j-1]][j-1];
                dfs(y,x);
            }
        }
    }
    
    int lca(int x,int y) {
        if (dep[x]<dep[y]) swap(x,y);
        for (int i=t;i>=0;i--)
            if (dep[f[x][i]]>=dep[y]) x=f[x][i];
        if (x==y) return x;
        for (int i=t;i>=0;i--)
            if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];    
        return f[x][0];    
    }
    
    LL dist(int x,int y) { return dis[x]+dis[y]-2*dis[lca(x,y)]; }
    
    int main()
    {
        cin>>n>>m;
        t=log(n)/log(2)+1;
        for (int i=1;i<n;i++) {
            int x,y,z; scanf("%d%d%d",&x,&y,&z);
            add_edge(x,y,z); add_edge(y,x,z);
        }
        dfs(1,0);
        LL ans=0;
        for (int i=1;i<=m;i++) {
            int x,l,r; scanf("%d",&x);
            if (!S.count(dfn[x])) {  //插入操作 
                S.insert(dfn[x]);
                it=S.lower_bound(dfn[x]); if (it==S.begin()) l=*--S.end(); else l=*--it;
                it=S.upper_bound(dfn[x]); if (it==S.end()) r=*S.begin(); else r=*it;
                l=idx[l]; r=idx[r];
                ans+=dist(l,x)+dist(x,r)-dist(l,r);
            } else {  //删除操作 
                it=S.lower_bound(dfn[x]); if (it==S.begin()) l=*--S.end(); else l=*--it;
                it=S.upper_bound(dfn[x]); if (it==S.end()) r=*S.begin(); else r=*it;
                l=idx[l]; r=idx[r];
                ans-=dist(l,x)+dist(x,r)-dist(l,r);
                S.erase(dfn[x]);
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    洛谷P2495

    建立虚树。然后dp[x]=min(dp[x],sigma(dp[y])) (y是x的儿子)。写了可以当虚树模板。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int N=5e5+10;
    const LL INF=1LL<<60;
    int n,m,k,t,a[N]; 
    
    int cnt=1,head[N],nxt[N<<1],to[N<<1],len[N<<1];
    void add_edge(int x,int y,int z) {
        nxt[++cnt]=head[x]; to[cnt]=y; len[cnt]=z; head[x]=cnt;
    }
    
    //-------------------------------LCA------------------------------
    int num=0,dfn[N],idx[N],dep[N],f[N][20]; LL Min[N];
    void dfs(int x,int fa) {
        dfn[x]=++num; idx[num]=x; dep[x]=dep[fa]+1;
        for (int i=head[x];i;i=nxt[i]) {
            int y=to[i];
            if (!dfn[y]) {
                Min[y]=min(Min[x],(LL)len[i]);
                f[y][0]=x;
                for (int j=1;j<=t;j++) f[y][j]=f[f[y][j-1]][j-1];
                dfs(y,x);
            }
        }
    }
    
    int LCA(int x,int y) {
        if (dep[x]<dep[y]) swap(x,y);
        for (int i=t;i>=0;i--)
            if (dep[f[x][i]]>=dep[y]) x=f[x][i];
        if (x==y) return x;
        for (int i=t;i>=0;i--)
            if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];    
        return f[x][0];    
    }
    
    bool cmp(int x,int y) { return dfn[x]<dfn[y]; }
    
    //-------------------------------虚树------------------------------ 
    vector<int> G[N];  //得到的虚树 
    int sk[N<<1],top=1;  //栈:从根到当前点的路径 
    
    void Insert(int x) {
        if (top==1) { sk[++top]=x; return; }
        int lca=LCA(x,sk[top]);
        if (lca==sk[top]) return;
        while (top>1 && dfn[sk[top-1]]>=dfn[lca])
            G[sk[top-1]].push_back(sk[top]),top--;
        if (lca!=sk[top]) G[lca].push_back(sk[top]),sk[top]=lca;
        sk[++top]=x;    
    }
    
    void Build() {
        sk[top=1]=1;  //先把根插入到栈 
        for (int i=1;i<=k;i++) Insert(a[i]);  //逐个点插入到虚树 
        for (;top;top--) G[sk[top-1]].push_back(sk[top]);  //把剩下点连边得到完整虚树 
    }
    
    //-------------------------------DP------------------------------ 
    LL dp_dfs(int x) {
        if (G[x].size()==0) return Min[x];
        LL ret=0;
        for (int i=0;i<G[x].size();i++) {
            int y=G[x][i];
            ret+=dp_dfs(y);
        }
        G[x].clear();  //关键:边dp边删除虚树,外部删除会TLE。 
        return min(ret,Min[x]);
    }
    
    int main()
    {
        cin>>n;
        t=log(n)/log(2)+1;
        for (int i=1;i<n;i++) {
            int x,y,z; scanf("%d%d%d",&x,&y,&z);
            add_edge(x,y,z); add_edge(y,x,z);
        }
        Min[1]=INF;
        dfs(1,0);  //倍增求LCA预处理 
        
        cin>>m;
        for (int i=1;i<=m;i++) {
            scanf("%d",&k);
            for (int j=1;j<=k;j++) scanf("%d",&a[j]);
            sort(a+1,a+k+1,cmp);
            Build();  //建立k个点的虚树 
            printf("%lld
    ",dp_dfs(1));  //在虚树上dp求出答案 
        }
        return 0;
    }
    View Code

    洛谷P3233

  • 相关阅读:
    bzoj2876 [Noi2012]骑行川藏
    关于线性基的一些理解
    bzoj2115 [Wc2011] Xor
    bzoj2884 albus就是要第一个出场
    bzoj2460 [BeiJing2011]元素
    bzoj2005 [Noi2010]能量采集
    关于积性函数的一些理解
    bzoj4300 绝世好题
    Servlet—文件上传
    JNDI—目录接口名
  • 原文地址:https://www.cnblogs.com/clno1/p/11044493.html
Copyright © 2020-2023  润新知