虚树(virtual tree)的概念

虚树 是将一个树的点集的某一个子集,以及该子集中点的 LCALCALCA 的集合,一起所重构出来的一棵树

虚树的用途

在树型dp中,有时候没必要对整颗树进行dp,只用对某个子集构成的虚树进行dp,大大降低了时空复杂度
例题:P2495 [SDOI2011] 消耗战

[SDOI2011] 消耗战

题目描述

在一场战争中,战场由 nnn 个岛屿和 n−1n-1n−1 个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为 111 的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他 kkk 个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。

侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到 111 号岛屿上)。不过侦查部门还发现了这台机器只能够使用 mmm 次,所以我们只需要把每次任务完成即可。

输入格式

第一行一个整数 nnn,表示岛屿数量。

接下来 n−1n-1n−1 行,每行三个整数 u,v,wu,v,wu,v,w ,表示 uuu 号岛屿和 vvv 号岛屿由一条代价为 www 的桥梁直接相连。

第 n+1n+1n+1 行,一个整数 mmm ,代表敌方机器能使用的次数。

接下来 mmm 行,第 iii 行一个整数 kik_iki​ ,代表第 iii 次后,有 kik_iki​ 个岛屿资源丰富。接下来 kik_iki​ 个整数 h1,h2,...,hkih_1,h_2,..., h_{k_i}h1​,h2​,...,hki​​ ,表示资源丰富岛屿的编号。

输出格式

输出共 mmm 行,表示每次任务的最小代价。

样例 #1

样例输入 #1

10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
3
2 10 6
4 5 7 8 3
3 9 4 6

样例输出 #1

12
32
22

提示

数据规模与约定

  • 对于 10%10\%10% 的数据,n≤10,m≤5n\leq 10, m\leq 5n≤10,m≤5 。
  • 对于 20%20\%20% 的数据,n≤100,m≤100,1≤ki≤10n\leq 100, m\leq 100, 1\leq k_i\leq 10n≤100,m≤100,1≤ki​≤10 。
  • 对于 40%40\%40% 的数据,n≤1000,1≤ki≤15n\leq 1000, 1\leq k_i\leq 15n≤1000,1≤ki​≤15 。
  • 对于 100%100\%100% 的数据,2≤n≤2.5×105,1≤m≤5×105,∑ki≤5×105,1≤ki<n,hi≠1,1≤u,v≤n,1≤w≤1052\leq n \leq 2.5\times 10^5, 1\leq m\leq 5\times 10^5, \sum k_i \leq 5\times 10^5, 1\leq k_i< n, h_i\neq 1, 1\leq u,v\leq n, 1\leq w\leq 10^52≤n≤2.5×105,1≤m≤5×105,∑ki​≤5×105,1≤ki​<n,hi​​=1,1≤u,v≤n,1≤w≤105 。

虚树的构建

虚树的重要性质:祖先后代的关系没有改变,是关键点的浓缩子树,例如下图所示(来自oiwiki)

以上述题为例子

第一步:初始化dfs序,f数组,g数组

对树上的每个节点,打上dfs序标记,构建 LCALCALCA 的树上倍增 fff 数组,这里有个小技巧,就是查询树上两点之间的简单路径的 最小/最大 权值,利用 ggg 数组:

1.初始化 ggg 数组:

rep(j,1,K) rep(i,1,n)    g[i][j]=min(g[i][j-1],g[f[i][j-1]][j-1]);

2.树上倍增法,查询祖先节点与子孙节点之间的权值的最值情况

inline int getw(int x,int y)
{if(d[x]>d[y])   swap(x,y);int ans=INF,i;repf(i,K,0)     if(d[f[y][i]]>=d[x])  ans=min(ans,g[y][i]),y=f[y][i];    return ans;
}
void getrd(int u,int fa,int h)       //求dfs序
{int v,e;rnk[u]=idx++;       //dfs序d[u]=h;              //深度数组,用于 LCA 树上倍增法for(e=head[u];e;e=table[e].nxt){v=table[e].to;if(v==fa)  continue;f[v][0]=u;                    //初始化 f 数组g[v][0]=table[e].val;        //初始化 g 数组getrd(v,u,h+1);}
}

第二步:构建虚树(核心)

  1. 首先将1号节点,即根节点加入栈中,(即使不是关键点,但更加统一化)
  2. 主体循环是从小到大(dfs序)遍历关键点序列,如下图理解:
void buildtr()   //建虚树
{int i,j,f,w,tph;q[hh]=1,pp[hh++]={rnk[1],1};k=read();rep(i,1,k)   q[hh]=read(),pp[hh]={rnk[q[hh]],q[hh]},isk[q[hh]]=true,hh++;sort(pp,pp+hh);st[ptr++]=1;            //st为栈 tph=hh-1;rep(f,1,tph)           //i为当前即将入栈的节点 {//初始化变量i=pp[f].s;tt=0;//弹栈auto anc=lca(i,st[ptr-1]);while(rnk[st[ptr-1]]>rnk[anc]) out[tt++]=st[--ptr];if(rnk[st[ptr-1]]<rnk[anc])   st[ptr++]=anc,q[hh++]=anc;out[tt++]=st[ptr-1];//入栈st[ptr++]=i; //链接 rep(j,0,tt-2)   w=getw(out[j],out[j+1]),add_(out[j],out[j+1],w),add_(out[j+1],out[j],w);}//最终链接rep(j,0,ptr-2)   w=getw(st[j],st[j+1]),add_(st[j],st[j+1],w),add_(st[j+1],st[j],w);
}

第三步:dp

时间复杂度分析:

构建LCA-ST的复杂度:O(NlogN)O(NlogN)O(NlogN)
构建虚树的时间复杂度:O(KlogN)O(KlogN)O(KlogN) ,其中 KKK为关键点的数量

总体代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<map>
#include<unordered_map>
#include<queue>#define me(x,y) memset(x,y,sizeof x)
#define rep(i,x,y) for(i=x;i<=y;++i)
#define repf(i,x,y) for(i=x;i>=y;--i)
#define lowbit(x) -x&x
#define inf 0x3f3f3f3f
#define INF 0x7fffffff
#define f first
#define s secondusing namespace std;
typedef long long ll;
typedef pair<int,int> PII;inline int read()
{int x=0,f=1;char ch=getchar();while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}return x*f;
}struct edge
{int to;int nxt;int val;
};const int N= 250010,K=20;
int n,m,k,cnt,cnt_,idx,hh,tt,ptr;int head[N],head_[N],d[N],q[N],rnk[N],st[N],out[N],f[N][K+3],g[N][K+3];
ll dp[N];
edge table[N*2],table_[N*2];
bool isk[N];
PII pp[N];inline void add(int& u,int& v,int& w)
{table[++cnt].nxt=head[u];head[u]=cnt;table[cnt].to=v;table[cnt].val=w;
}inline void add_(int& u,int& v,int& w)
{table_[++cnt_].nxt=head_[u];head_[u]=cnt_;table_[cnt_].to=v;table_[cnt_].val=w;
}inline int lca(int x,int y)        //求lca
{int i;if(d[x]>d[y]) swap(x,y);repf(i,K,0)   if(d[f[y][i]]>=d[x])  y=f[y][i];if(x==y) return x;repf(i,K,0) if(f[x][i]!=f[y][i])  x=f[x][i],y=f[y][i];return f[x][0];
}inline int getw(int x,int y)
{if(d[x]>d[y])   swap(x,y);int ans=INF,i;repf(i,K,0)     if(d[f[y][i]]>=d[x])  ans=min(ans,g[y][i]),y=f[y][i];    return ans;
}void buildf()                      //建立f表
{int i,j;rep(j,1,K) rep(i,1,n)  f[i][j]=f[f[i][j-1]][j-1],g[i][j]=min(g[i][j-1],g[f[i][j-1]][j-1]);
} void getrd(int u,int fa,int h)        //求dfs序
{int v,e;rnk[u]=idx++;d[u]=h;for(e=head[u];e;e=table[e].nxt){v=table[e].to;if(v==fa)   continue;f[v][0]=u;g[v][0]=table[e].val; getrd(v,u,h+1);}
}void m_clear() //清理
{int i;rep(i,0,hh-1)    isk[q[i]]=head_[q[i]]=dp[q[i]]=0; cnt_=hh=tt=ptr=0;
}void buildtr() //建虚树
{int i,j,f,w,tph;q[hh]=1,pp[hh++]={rnk[1],1};k=read();rep(i,1,k)   q[hh]=read(),pp[hh]={rnk[q[hh]],q[hh]},isk[q[hh]]=true,hh++;sort(pp,pp+hh);st[ptr++]=1;            //st为栈 tph=hh-1;rep(f,1,tph)           //i为当前即将入栈的节点 {//初始化变量i=pp[f].s;tt=0;//弹栈auto anc=lca(i,st[ptr-1]);while(rnk[st[ptr-1]]>rnk[anc]) out[tt++]=st[--ptr];if(rnk[st[ptr-1]]<rnk[anc])   st[ptr++]=anc,q[hh++]=anc;out[tt++]=st[ptr-1];//入栈st[ptr++]=i; //链接 rep(j,0,tt-2)   w=getw(out[j],out[j+1]),add_(out[j],out[j+1],w),add_(out[j+1],out[j],w);}//最终链接rep(j,0,ptr-2)   w=getw(st[j],st[j+1]),add_(st[j],st[j+1],w),add_(st[j+1],st[j],w);
}void getdp(int u,int fa)
{int e,v;for(e=head_[u];e;e=table_[e].nxt){v=table_[e].to;if(v==fa)    continue;getdp(v,u);if(isk[v])  dp[u]+=table_[e].val;else dp[u]+=min((ll)table_[e].val,dp[v]);}
}int main()
{int i,j,u,v,w;n=read();rep(i,1,n-1)   {u=read(),v=read(),w=read();add(u,v,w),add(v,u,w); }getrd(1,1,1);buildf();m=read();while(m--){buildtr();getdp(1,1);printf("%lld\n",dp[1]);m_clear();  }return 0;
}

数据结构专题——虚树相关推荐

  1. 牛客竞赛数据结构专题班树状数组、线段树练习题

    F.little w and Discretization 题意:找区间[l,r]内离散化后和原来的值不同大小的数的个数 思路:先求区间mex,同时记录区间有多少个数,再 用区间长度减去(区间内小于m ...

  2. 图论专题-学习笔记:虚树

    图论专题-学习笔记:虚树 1. 前言 2. 详解 2.1 虚树定义 2.2 虚树构造 2.3 例题 3. 总结 4. 参考资料 1. 前言 虚树,主要是用于一类树上问题,这类问题通常是需要取一些关键点 ...

  3. 数据结构专题-学习笔记:李超线段树

    数据结构专题 - 学习笔记:李超线段树 1. 前言 2. 详解 3. 应用 4. 总结 5. 参考资料 1. 前言 本篇博文是博主学习李超线段树的学习笔记. 2020/12/21 的时候我在 线段树算 ...

  4. 数据结构入门9—虚树

    原本以为K-D Tree比虚树难,但是后来发现虚树反而难理解一些.主要是关于找LCA的问题. 假如我们有一些点是一定要在虚树里的,那么需要保证他们任意两点的LCA也在虚树里,否则树的结构就改变了. 那 ...

  5. 数据结构 非线性结构 树 介绍及存储方法

    所谓树, 其实跟链表有类似的地方,  就是都是由节点和指针构成的数据结构. 在链表中,  每1个节点(尾节点除外)只有1个指针指向下1个节点. 所以链表各个节点可以由一条线链接起来, 就是一种线性结构 ...

  6. NOIp 数据结构专题总结 (1):STL、堆、并查集、ST表、Hash表

    系列索引: NOIp 数据结构专题总结 (1) NOIp 数据结构专题总结 (2) STL structure std::vector #include <vector> std::vec ...

  7. 牛客多校1 - Infinite Tree(虚树+换根dp+树状数组)

    题目链接:点击查看 题目大意:给出一个无穷个节点的树,对于每个大于 1 的点 i 来说,可以向点 i / minvid[ i ] 连边,这里的 mindiv[ x ] 表示的是 x 的最小质因数,现在 ...

  8. NOI数据结构:后缀树

    NOI数据结构:后缀树 后缀树_fanzitao的专栏-CSDN博客_后缀树 后缀树 - sangmado - 博客园 字符串-后缀树和后缀数组详解 字符串-后缀树和后缀数组详解_吴泽龙的博客-CSD ...

  9. 虚树学习笔记(洛谷2495 消耗战)

    题目链接 因为辣鸡csdn,导致之前快写好的博客没了 QWQ悲伤逆流成河qwqqq 首先虚树,这个东西,我感觉是一种思想,或者是方法,而并不是一个数据结构什么的. 他主要是用来解决:给出一棵树,每次询 ...

最新文章

  1. TweenMax动画库学习(三)
  2. mysql dba系统学习(4)mysql的多实例multi启动停止
  3. SAP扫盲系列之二:SAP ABAP应用服务器的组成部分
  4. iPhone开发资料之内存管理 ,循环引用导致的内存问题
  5. 传说中理科生看到会沉默、文科生看到会流泪的【程序员文史综合题目】
  6. 主页面功能的java_6-04-项目实战-主页面显示当前用户退出功能实现
  7. Linux 常用命令随笔(二)
  8. linux用户和组基础
  9. 鸿蒙战略看点,鸿蒙OS四大看点详解!华为的野心不止手机,而是“一统江湖”...
  10. grep 多模式匹配
  11. 今天谈谈COLING2018计算语言学进展
  12. 从我的公众号谈执行力
  13. c语言 实现参数值双向传递,基于C语言函数参数传递规律的探讨
  14. 新浪微博客户端开发之发布微博,Android面试题
  15. 安装CARLA Simulator错误 安装失败 0x80070005 - 访问被拒绝 Error Setup Failed 0x80070005 - Access is denied
  16. android平板的隐藏空间如何开启,平板电脑怎么截图和怎么隐藏游戏?
  17. linux proftpd 用户,proftpd 虚拟帐号的建立及quota
  18. JAVA 开发命名规范——阿里巴巴Java开发手册
  19. 解决树莓派4B无线鼠标迟滞/延迟的问题
  20. WIN10桌面图标变成白文件的一种解决方法

热门文章

  1. maven 打的jar包很小
  2. LM小型可编程控制器软件(基于CoDeSys)笔记十七:pto脉冲功能块
  3. 阿里云(三) Ubuntu系统下mysql卸载
  4. EtherCAT设备协议详解一、EtherCAT概述
  5. linux恢复安卓数据,安卓数据恢复2 - ranfs的个人空间 - OSCHINA - 中文开源技术交流社区...
  6. 你还在担心你的 IP 被封吗?
  7. Python正则表达式中的转义问题\\\\\\\\\????(焯!什么鬼)
  8. 十年陌陌,是否能成为Hello
  9. VUE利用transition标签实现摇一摇抽签效果
  10. 新书自序【人人都是产品经理:9073】