首页 > 其他分享 >虚树+树形dp

虚树+树形dp

时间:2024-09-10 22:21:59浏览次数:3  
标签:sta int top 树形 lca 虚树 dp dis

虚树实际上是一颗浓缩子树;使用虚树的题目大部分具有查询的点有限,同时虚树构建的信息符合

规则;

做虚树的题目:步骤为先想出原树的暴力求解做法,然后构建虚树同时向其中添加有用信息(比如边权);

虚树的构建过程:

虚树的构建大致有两种,但是两种方式都与dfs序有关;首先解释为什么与dfs序有关

我们可以知道,虚树的构建是求各个关键点两两的lca的过程。如果是两两求lca,暴力构建虚树的话,复杂度会达到k*k*logn;dfs序的出现可以将复杂度降为klogn;dfs序排序可以知道的是,当两个关键点是在同一个链上,那么祖先为其中一个点,并且祖先为深度较小的那个点。当两个关键点不在同一条链上,那么他们的祖先一定是这两个关键点的子树内关键点的lca;

我们做那么一个事情:

如果两两关键点x,y之间的lca为其中一个,那么可以知道他们之间一定没有关键点(因为他们是相邻的两个点,如果中间存在关键点z的话,dfs[x]<dfs[z]<dfs[y],与现有条件冲突),那我们是否可以直接连边呢?我们考虑一下,x,y之间可能出现非关键点吗,是可以的,只存在一种情况:

y后面存在一个点t,使得lca(y,t)在x,y之间,那我们可以知道x->lca(y,t),但是有没有可能存在一个点t1,使得lca(t1,t),使得lca(t1,t)在x,lca(y,t)之间呢,也是存在的。似乎这种解法到了尽头。不妨我们再挖掘一下性质,假设现在按照dfs序排序有a1,a2,a3,a4,a5这5个点;假设我们处理到a4这个点,求出lca(a3,a4),如果lca(a3,a4)在前面没有出现过,那么lca(a3,a4)一定是虚树上面的点,加入集合中(我们也可以一股脑全部加进去,最后去重即可);那么对于lca(a1,a4),lca(a2,a4)呢,这几个点不需要处理吗,难道这两个点一定出现过吗?

下面给出证明:

对于dfs小于a3的点,即ai(1<=i<3)一定在a3的左边或者是祖先;

如果是祖先,那么lca(ai,a4)一定为lca(a3,a4)或者ai;如果不是祖先,那么lca(ai,a4)一定为lca(a3,a4)或者lca(ai,a3);所以一定是出现过呢

一.线性构建

当我们求出虚树的所有点后,将其按照dfs序进行排序,按照dfs序来构建树;

对于x,y,连接lca(x,y)->y,为什么这个就是正确的呢?

如果 x是 y 的祖先,那么直接连边。因为 DFS 序保证x和 y的 DFS 序是相邻的,所以x到y的路径上面没有虚树上的点。

如果 x不是y的祖先,那么就把lca(x,y)当作y的的祖先,因为 DFS 序保证x和 y的 DFS 序是相邻的,同时x,y分别在lca(x,y)的两颗子树上,如果lca(x,y)->y的路径上存在虚树的点,那么这个点才是x,这一点和x,y分别在lca(x,y)的两颗子树上相互矛盾,于是lca(x,y)->y上一定不存在虚树的点。

根据上面的构建,我们将虚树构建出来,所有的虚树的点都存在且结构为一颗树

代码来自虚树 - OI Wiki

int dfn[maxn];
int h[maxn], m, a[maxn], len;  // 存储关键点

bool cmp(int x, int y) {
  return dfn[x] < dfn[y];  // 按照 dfs 序排序
}

void build_virtual_tree() {
  sort(h + 1, h + m + 1, cmp);  // 把关键点按照 dfs 序排序
  for (int i = 1; i < m; ++i) {
    a[++len] = h[i];
    a[++len] = lca(h[i], h[i + 1]);  // 插入 lca
  }
  a[++len] = h[m];
  sort(a + 1, a + len + 1, cmp);  // 把所有虚树上的点按照 dfs 序排序
  len = unique(a + 1, a + len + 1) - a - 1;  // 去重
  for (int i = 1, lc; i < len; ++i) {
    lc = lca(a[i], a[i + 1]);
    conn(lc, a[i + 1]);  // 连边,如有边权 就是 distance(lc,a[i+1])
  }
}

二.单调栈构建

1.对k个关键点进行dfs序排序

2.单调栈维护一个深度由小到大的点

3.考虑加入a[i]

1.假设lca=LCA(sta[top],x),那么如果lca=s[top],直接入栈

如果lca!=sta[top],那么sta[top]和x是lca两颗不同子树内的节点,那么这样的话,低于lca深度的点都需要连边,因为x后面的点和x前面的关键点的公共祖先一定在lca处或者比lca深度更低的点了;

我们一直出栈,直到dep[lca] >dep[sta[top-1]];

如果这时候lca=sta[top],说明lca已经在栈中,直接把a[i]入栈

如果lca!=sta[top],那么不在栈中,将lca与sta[top]连边,同时将a[i]入栈

单调栈的本质是在维护虚树的一条链,链结束的特征是lca的深度,因为我们是dfs序;

 

代码:

bool cmp(int a, int b){
    return dfn[a]<dfn[b];
}
void build(int k){
    sort(a+1,a+k+1,cmp);
    idx=0;
    sta[top=1]=1;
    if(a[1]!=1)sta[++top]=a[1];
    dp[1]=0;
    dp[a[1]]=0;
    for(int i=2; i<=k; i++){
        int l=lca(sta[top],a[i]);
        dp[l]=0;
        dp[a[i]]=0;
        while(top>1 && dep[sta[top-1]]>=dep[l]){
            lca(sta[top-1],sta[top]);
            add(sta[top-1],sta[top],dis);
            top--;
        }
        if(l!=sta[top]){
            lca(sta[top],l);
            add(l,sta[top],dis);
            sta[top]=l;
        }
        sta[++top]=a[i];
    }
    while(top){
        lca(sta[top-1],sta[top]);
        add(sta[top-1],sta[top],dis);
        top--;
    }
}

题目:[SDOI2011] 消耗战 - 洛谷

思路:构建虚树,同时通过倍增预处理,使得我们在lca时求出两个点之间的最小代价;

最后就是一个简单的dp,设dp[u]为以u为根的最小代价;

对于出边v:

如果v是关键点dp[u]+=w[u->v];

如果v不是关键点dp[u]+=min(dp[v],w[u->v]);

具体代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=3*1e5+10,M=5*1e5+10,INF=1e9+11;
int n,m,k,a[N];

int h[N],ne[M<<1],e[M<<1],idx;
LL w[M<<1];
LL dp[N];
int sta[N<<1],top;

int dep[N],fa[N][20],dist[N][20],dis,dfn[N],cnt,siz[N];
void add(int a, int b,int c){
    e[++idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx;
}
LL ans=0;
void dfs(int u, int f){
    dfn[u]=++cnt;
    dep[u]=dep[f]+1;
    fa[u][0]=f;
    for(int i=1; i<=19; i++){
        fa[u][i]=fa[fa[u][i-1]][i-1];
        dist[u][i]=min(dist[u][i-1],dist[fa[u][i-1]][i-1]);
    }
    for(int i=h[u]; i; i=ne[i]){
        int v=e[i];
        if(v==f)continue;
        dist[v][0]=w[i];
        dfs(v,u);
    }
}

int lca(int a, int b){
    dis=INF;
    if(dep[a]<dep[b])swap(a,b);
    for(int i=19; ~i; i--){
        if(dep[fa[a][i]]>=dep[b]){
            dis=min(dis,dist[a][i]);
            a=fa[a][i];
        }
    }
    if(a==b)return b;
    
    for(int i=19; ~i; i--){
         if(fa[a][i]!=fa[b][i]){
            dis=min(dis,dist[a][i]);
            dis=min(dis,dist[b][i]);
            a=fa[a][i];b=fa[b][i];
          
        }
    }
    dis=min(dis,dist[a][0]);
    return fa[a][0];
}
bool cmp(int a, int b){
    return dfn[a]<dfn[b];
}
void build(int k){
    sort(a+1,a+k+1,cmp);
    idx=0;
    sta[top=1]=1;
    if(a[1]!=1)sta[++top]=a[1];
    dp[1]=0;
    dp[a[1]]=0;
    for(int i=2; i<=k; i++){
        int l=lca(sta[top],a[i]);
        dp[l]=0;
        dp[a[i]]=0;
        while(top>1 && dep[sta[top-1]]>=dep[l]){
            lca(sta[top-1],sta[top]);
            add(sta[top-1],sta[top],dis);
            top--;
        }
        if(l!=sta[top]){
            lca(sta[top],l);
            add(l,sta[top],dis);
            sta[top]=l;
        }
        sta[++top]=a[i];
    }
    while(top){
        lca(sta[top-1],sta[top]);
        add(sta[top-1],sta[top],dis);
        top--;
    }
}

void solve(int x){

    for(int i=h[x]; i; i=ne[i]){
            int v=e[i];
            solve(v);
            if(siz[v]){
               
                dp[x]+=w[i];
            }
            else dp[x]+=min(dp[v],w[i]);
          
    }

    h[x]=0;

}

int main(){
    
   cin>>n;
   for(int j=0; j<20; j++)dist[0][j]=1e9;
    for(int i=1; i<n; i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
        for(int j=0; j<20; j++)dist[i][j]=1e9;
    }
    dfs(1,0);

    memset(h,0,sizeof h);
    cin>>m;
    while(m--){
        int k;
        cin>>k;
        for(int i=1; i<=k; i++){
            cin>>a[i];
            siz[a[i]]=1;
            
        }
   
        build(k);
   
  
        solve(1);
        cout<<dp[1]<<endl;
        for(int i=1; i<=k; i++){
            siz[a[i]]=0;
        }
    }
}

标签:sta,int,top,树形,lca,虚树,dp,dis
From: https://blog.csdn.net/2403_86761086/article/details/142076747

相关文章

  • 线性dp:LeetCode122.买卖股票的最佳时机ll
    买卖股票本文所讲解的内容与LeetCode122.买卖股票的最佳时机ll,这道题题意相同,阅读完本文后可以自行挑战一下力扣链接题目叙述:给定一个长度为N的数组,数组中的第i个数字表示一个给定股票在第i天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交......
  • Wordpress采集发布插件-免费下载
    推荐一款可以自动采集文章数据,并发布到Wordpress网站的Wordpress采集发布插件,支持对接简数采集器,火车头采集器,八爪鱼采集器,后羿采集器等大多数网页文章数据采集软件。Wordpress采集发布插件使用方法如下:1. 下载并安装Wordpress采集发布插件1.1 Wordpress采集发布插件免费下载......
  • DP
    动态规划动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。三个条件:最优子结构,无后效性和子问题重叠。最优子结构证明问题最优解的第一个组成部分是做出一个选择;对于一个给定问题,在其可能的第一步选择中,假定你已经知道哪种选择才会得到最优解......
  • [DPDK] dumpcap报错EAL init failed: is primary process running?解决办法
    [DPDK]dumpcap报错EALinitfailed:isprimaryprocessrunning?解决办法问题我写了一个DPDK程序,现在想要用DPDK自带的dpdk-dumpcap工具来抓包测试。根据官网描述,我们需要先启动我们的程序为主进程,然后启动dpdk-dumpcap为副进程。但是我直接运行dpdk-dumpcap,显示如下错误:注:......
  • TCP和UDP对比
    TCP和UDP对比TCP(TransmissionControlProtocol,传输控制协议)和UDP(UserDatagramProtocol,用户数据报协议)是两种常用的网络传输层协议,它们在网络通信中扮演着重要的角色。以下是它们的主要区别:连接性:TCP:TCP是一种面向连接的协议,在数据传输之前需要建立一个连接(三次握手),数据......
  • TCP和UDP
    TCP(TransmissionControlProtocol,传输控制协议)和UDP(UserDatagramProtocol,用户数据报协议)是两种常用的网络传输层协议,它们在网络通信中扮演着重要的角色。以下是它们的主要区别:连接性:TCP:是一种面向连接的协议。在数据传输开始之前,必须建立一个连接,通过三次握手过程来确保......
  • el-table树形懒加载表格展开后 子节点修改数据后实时刷新
    问题描述在项目中遇到一个关于el-table的懒加载树型结构修改数据后需要刷新数据的问题,需要手动刷新页面之后才能刷新问题解决:1.首先创建map来用于存取数据,constloadMap=newMap();//存储load加载的子节点以能够编辑后更新2.在table展开子节点时,用map存下每次被加载......
  • NPDP和PMP有啥区别?其实就这个3点
    1、发证机构不同PMP是由项目管理协会PMI发起,严格评估项目管理人员知识技能是否具有高品质的资格认证考试。1999年,中国国际人才交流基金会将项目管理协会制定的《项目管理知识体系指南》和“项目管理专业人士职业资格认证”引入中国。NPDP是由美国产品开发与管理协会PDMA......
  • WPF DataGridTemplateColumn.CellTemplate Command CommandParameter
    <DataGridTemplateColumnHeader="Image"><DataGridTemplateColumn.CellTemplate><DataTemplate><ButtonCommand="{BindingDataContext.EnterCmd,RelativeSource={RelativeSourceFindAn......
  • 【题解】Solution Set - NOIP2024集训Day25 概率期望 dp
    【题解】SolutionSet-NOIP2024集训Day25概率期望dphttps://www.becoder.com.cn/contest/5515「QOJ2606」Gachapon\(f_{i,j}\):用一次合法的level-irolling能够抽到的\(j\)的期望个数。\(h_{i,j,k}\):在\(i\)次操作之内,抽到恰好\(k\)个\(j\)的概率。\[h_{i,j,k......