1将一颗大树浓缩成小树,因为在一棵树中,有些信息是不必要的,只需要关注那些关键信息,保留原有的父子关系,从而方便树形dp之类的信息维护,降低时间复杂度。
2建立虚树需要以下操作:
(1)进行一次dfs,将关键点(涉及到询问的点)按照dfs序进行排序。
(2)创建一个栈,起初栈只有一个根节点,这里需要用栈来保证栈中元素形成了一条链
(3)关键点依次插入栈中,获取当前点和栈顶元素的lca,一开始放入栈中元素的规律是从上到下形成一条链,当出现一个不在这条链上的关键点是,这个点和当前栈顶的点(就是当前链的最末端)的lca必定在这条链上,从末端沿着向上找到lca所处的位置,(dfn[top-1]
(4)因此,虚树虚树中保存的就是栈顶与关键点的lca以及关键点。
代码实现:
按照模板题:[SDOI2011] 消耗战 - 洛谷
思路:用倍增法计算lca,同时根据倍增思想保存u到达往上2^i经过的边的最小值,根据最小边权进行虚树创建,最后进行dp
1数据存储
- const int maxn=2e5+5e4+10;
- const int inf=0x3f3f3f3f;
- struct edge{
- int to;
- ll w;
- };
- vector
vv[maxn];//记录树 - vector
vec[maxn];//记录虚树 - int stk[maxn],top;//模拟栈
- int dfn[maxn],cnt;
- int n,dep[maxn],fa[maxn][30];
- ll dis[maxn][22];//记录u点往上2^j的祖先节点这段路径中最小的边权
- int q[maxn],m;//关键点
- ll dp[maxn];
- bool vis[maxn];//标记关键点
- int root=1;//树的根节点
2第一次dfs,获取dfs序,向上2^0的父节点及最小边权,深度
- void dfs1(int u,int fat)//获取dfs序,父节点,以及通往父节点线段的长度
- {
- dfn[u]=++cnt;
- fa[u][0]=fat;
- dep[u]=dep[fat]+1;
- for(edge p:vv[u])
- {
- int to=p.to;
- int w=p.w;
- if(fat==to)
- continue;
- dis[to][0]=p.w;
- dfs1(to,u);
- }
- }
3预处理倍增所用的数组
- void pre_work()//预处理祖先节点和路径最小线段
- {
- for(int j=1;j<=17;j++)
- for(int i=1;i<=n;i++)
- {
- fa[i][j]=fa[fa[i][j-1]][j-1];
- dis[i][j]=min(dis[i][j-1],dis[fa[i][j-1]][j-1]);
- }
- }
4在建虚树过程中,获取两点之间的边权
- ll query(int u,int v)//获取uv之间的路径
- {
- ll ans=inf;
- for(int i=17;i>=0;i--)//u从下往上倍增往上爬
- {
- if(fa[u][i]&&dep[fa[u][i]]>=dep[v])
- {
- ans=min(ans,dis[u][i]);//寻找其中最短路径
- u=fa[u][i];
- }
- }
- return ans;
- }
5建边(原本的数,虚树)
- inline void add1(int u,int v,int w)//添加树的边
- {
- vv[u].push_back({v,w});
- vv[v].push_back({u,w});
- }
- inline void add2(int u,int v)//添加虚树的边
- {
- ll w=query(v,u);
- vec[u].push_back({v,w});
- }
6构建虚树
- bool cmp(const int &a,const int &b)//按照dfs序排序
- {
- return dfn[a]
- }
- void insert(int u)
- {
- if(u==1)
- return;
- int l=lca(u,stk[top]);
- while(top>1&&dfn[stk[top-1]]>=dfn[l])//寻找dfn[top-1]
- {
- add2(stk[top-1],stk[top]);
- top--;
- }
- if(stk[top]!=l)//如果顶端不是lca,那么说明lca不存在,就插入栈中
- {
- add2(l,stk[top]);
- stk[top]=l;
- }
- stk[++top]=u;
- }
- inline void build()
- {
- sort(q+1,q+1+m,cmp);
- top=0;
- stk[++top]=root;
- for(int i=1;i<=m;i++)
- insert(q[i]);
- while(top>1)//将最后一条链加入虚树中
- {
- add2(stk[top-1],stk[top]);
- top--;
- }
- }
7第二次dfs,进行树形dp
- void dfs2(int u)
- {
- for(int i=0;i
size();i++) - {
- edge p=vec[u][i];
- int to=p.to;
- ll w=p.w;
- dfs2(to);
- if(vis[to])//如果to为关键点,这条边必须切断
- dp[u]+=w;
- else
- dp[u]+=min(w,dp[to]);//如果不是关键点,可以切断to需要切断的或者直接切断to这条路径
- vis[to]=0;
- dp[to]=0;
- }
- vec[u].clear();
- }
8主函数
- int main(){
- cin>>n;
- for(int i=1;i
- {
- int u,v,w;
- cin>>u>>v>>w;
- add1(u,v,w);
- }
- dfs1(root,0);
- pre_work();
- int t;
- cin>>t;
- while(t--)
- {
- cin>>m;
- for(int i=1;i<=m;i++)
- {
- cin>>q[i];
- vis[q[i]]=1;
- }
- build();
- dfs2(root);
- cout<
1]<<'\n'; - dp[1]=0;
- }
- return 0;
- }
9完整代码
- #include
- #define ll long long
- #define IOS ios::sync_with_stdio(false);cin.tie(0)
- using namespace std;
- const int maxn=2e5+5e4+10;
- const int inf=0x3f3f3f3f;
- struct edge{
- int to;
- ll w;
- };
- vector
vv[maxn];//记录树 - vector
vec[maxn];//记录虚树 - int stk[maxn],top;//模拟栈
- int dfn[maxn],cnt;
- int n,dep[maxn],fa[maxn][30];
- ll dis[maxn][22];//记录u点往上2^j的祖先节点这段路径中最小的边权
- int q[maxn],m;//关键点
- ll dp[maxn];
- bool vis[maxn];//标记关键点
- int root=1;//树的根节点
- ll query(int u,int v)//获取uv之间的路径
- {
- ll ans=inf;
- for(int i=17;i>=0;i--)//u从下往上倍增往上爬
- {
- if(fa[u][i]&&dep[fa[u][i]]>=dep[v])
- {
- ans=min(ans,dis[u][i]);//寻找其中最短路径
- u=fa[u][i];
- }
- }
- return ans;
- }
- inline void add1(int u,int v,int w)//添加树的边
- {
- vv[u].push_back({v,w});
- vv[v].push_back({u,w});
- }
- inline void add2(int u,int v)//添加虚树的边
- {
- ll w=query(v,u);
- vec[u].push_back({v,w});
- }
- void pre_work()//预处理祖先节点和路径最小线段
- {
- for(int j=1;j<=17;j++)
- for(int i=1;i<=n;i++)
- {
- fa[i][j]=fa[fa[i][j-1]][j-1];
- dis[i][j]=min(dis[i][j-1],dis[fa[i][j-1]][j-1]);
- }
- }
- int lca(int x,int y)//获取最近公共祖先
- {
- if(x==y)
- return x;
- if(dep[x]
- swap(x,y);
- for(int i=17;i>=0;i--)
- if(fa[x][i]&&dep[fa[x][i]]>=dep[y])
- x=fa[x][i];
- if(x==y)
- return x;
- for(int i=17;i>=0;i--)
- {
- if(fa[x][i]&&fa[y][i]&&fa[x][i]!=fa[y][i])
- {
- x=fa[x][i];
- y=fa[y][i];
- }
- }
- return fa[x][0];
- }
- void dfs1(int u,int fat)//获取dfs序,父节点,以及通往父节点线段的长度
- {
- dfn[u]=++cnt;
- fa[u][0]=fat;
- dep[u]=dep[fat]+1;
- for(edge p:vv[u])
- {
- int to=p.to;
- int w=p.w;
- if(fat==to)
- continue;
- dis[to][0]=p.w;
- dfs1(to,u);
- }
- }
- void dfs2(int u)
- {
- for(int i=0;i
size();i++) - {
- edge p=vec[u][i];
- int to=p.to;
- ll w=p.w;
- dfs2(to);
- if(vis[to])//如果to为关键点,这条边必须切断
- dp[u]+=w;
- else
- dp[u]+=min(w,dp[to]);//如果不是关键点,可以切断to需要切断的或者直接切断to这条路径
- vis[to]=0;
- dp[to]=0;
- }
- vec[u].clear();
- }
- bool cmp(const int &a,const int &b)//按照dfs序排序
- {
- return dfn[a]
- }
- void insert(int u)
- {
- if(u==1)
- return;
- int l=lca(u,stk[top]);
- while(top>1&&dfn[stk[top-1]]>=dfn[l])//寻找dfn[top-1]
- {
- add2(stk[top-1],stk[top]);
- top--;
- }
- if(stk[top]!=l)//如果顶端不是lca,那么说明lca不存在,就插入栈中
- {
- add2(l,stk[top]);
- stk[top]=l;
- }
- stk[++top]=u;
- }
- inline void build()
- {
- sort(q+1,q+1+m,cmp);
- top=0;
- stk[++top]=root;
- for(int i=1;i<=m;i++)
- insert(q[i]);
- while(top>1)//将最后一条链加入虚树中
- {
- add2(stk[top-1],stk[top]);
- top--;
- }
- }
- int main(){
- cin>>n;
- for(int i=1;i
- {
- int u,v,w;
- cin>>u>>v>>w;
- add1(u,v,w);
- }
- dfs1(root,0);
- pre_work();
- int t;
- cin>>t;
- while(t--)
- {
- cin>>m;
- for(int i=1;i<=m;i++)
- {
- cin>>q[i];
- vis[q[i]]=1;
- }
- build();
- dfs2(root);
- cout<
1]<<'\n'; - dp[1]=0;
- }
- return 0;
- }
-
相关阅读:
图扑数字孪生智慧加油站,构建安全防护网
C++ string常用函数用法总结
QT+OSG/osgEarth编译之四十四:osgText+Qt编译(一套代码、一套框架,跨平台编译,版本:OSG-3.6.5工具库osgText)
16_开发工具IntelliJ IDEA
Visual Studio 2022 设置 PySide6 扩展工具
动态内存与智能指针
Shell开发实践:服务器的磁盘、CPU、内存的占用监控
医疗IT系统安科瑞隔离电源装置在医院的应用
聚类算法模型评价指标
【等保小知识】等级保护单项测评包括哪些项目?
-
原文地址:https://blog.csdn.net/m0_63737271/article/details/125899654