题目戳我

\(\text{Solution:}\)

题目很显然可以设\(dp[i]\)表示\(i\)的子树内的关键点都不和\(i\)联通的最小待机,有如下\(dp\)方程:

\(v\in son_u,v\in key:dp[u]+=dis(u,v)\)

\(v\in son_u,v\not\in key:dep[u]+=\min(dis(u,v),dp[v])\)

但是暴力\(dp\)复杂度\(O(nm).\)观察\(k\)的大小发现,每一次都是有很多点不需要\(dp\)的。

于是我们可以考虑把原树根据所\(dp\)的关键点浓缩成一棵小一些的树,这便是虚树

虚树中包含的点只有各个关键点及其\(LCA,LCA\)的\(LCA\)等。

考虑用单调栈维护链来把这个虚树构造出来。

首先处理出\(LCA\),这里我选择的倍增。然后处理出字典序。

对每一次的关键点,对其按照字典序排序后,把根加入栈中。

对于下一次新加入的点:

  • 先求出当前要入栈的点和栈顶的\(LCA\).并向后比对。大于它深度的弹出栈,对每一个点在构造虚树的时候把其父亲记录下来。

  • 若当前栈顶元素等于\(LCA\)则终止弹栈。

  • 若当前栈顶元素的下一个元素深度小于\(LCA\),则将当前栈顶元素的父亲认为\(LCA\)并弹出此栈顶,终止弹栈。

  • 终止后,若\(LCA\)没有进过栈,则将其入栈,并将其父亲认为其入栈前的栈顶元素。

  • 最后,当前元素入栈,认父亲为当前栈顶元素。

  • 用一个数组将所有进过栈的元素储存下来,这便是虚树中的所有元素。又因为之前记录过父亲,这虚树就可以被还原出来。

  • 对数组中的元素按照字典序排序后用非递归的方式进行\(dp\)即可。

非虚树代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+10;
int dp[MAXN],n,m,h[MAXN],tot,head[MAXN];
struct E{int nxt,to,dis;}e[MAXN];
int vis[MAXN];
inline void add(int x,int y,int w){
e[++tot]=(E){head[x],y,w};
head[x]=tot;
}
void dfs(int x,int fa){
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
if(j==fa)continue;
dfs(j,x);
if(vis[j])dp[x]+=e[i].dis;
else dp[x]+=min(dp[j],e[i].dis);
dp[j]=0;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;++i){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
scanf("%d",&m);
while(m--){
int x;
scanf("%d",&x);
for(int i=1;i<=x;++i){
scanf("%d",h+i);
vis[h[i]]=1;
}
dfs(1,0);printf("%d\n",dp[1]);dp[1]=0;
for(int i=1;i<=x;++i)vis[h[i]]=0;
}
return 0;
}

虚树代码(附注释):

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+10;
const int inf=(1<<30);
char buf[1<<21],*p1=buf,*p2=buf;
inline int read(){
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char ch=gc();int s=0;
while(!isdigit(ch))ch=gc();
while(isdigit(ch))s=s*10-48+ch,ch=gc();
return s;
}
int tot,id,dfn[MAXN],n,m,st[MAXN],dp[MAXN],M[MAXN][22],pa[MAXN];
int f[MAXN][22],head[MAXN],h[MAXN],top,dep[MAXN],vis[MAXN],L;
struct E{int nxt,to,dis;}e[MAXN];
inline void add(int x,int y,int w){
e[++tot]=(E){head[x],y,w};
head[x]=tot;
}
int TM,yy[MAXN],val[MAXN];
long long ans[MAXN];
void dfs(int x,int fa){
dfn[x]=++id,dep[x]=dep[fa]+1,f[x][0]=fa;
for(int i=1;i<=20;++i){
f[x][i]=f[f[x][i-1]][i-1];
M[x][i]=min(M[x][i-1],M[f[x][i-1]][i-1]);
}
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
if(j==fa)continue;
M[j][0]=e[i].dis;
dfs(j,x);
}
}
int lca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=21;i>=0;--i)if(dep[x]<=dep[y]-(1<<i))y=f[y][i];
if(x==y)return x;
for(int i=21;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];//LCA
}
bool cmp(int x,int y){return dfn[x]<dfn[y];}
int dis(int x,int y){
int ans=inf;
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;--i){
if(dep[f[x][i]]>=dep[y])
ans=min(ans,M[x][i]),x=f[x][i];
if(x==y)return ans;
}
for(int i=21;i>=0;--i)if(f[x][i]!=f[y][i])ans=min(ans,min(M[x][i],M[y][i])),x=f[x][i],y=f[y][i];
return ans;//求两点间距离最小值
}
void Build(){
sort(h+1,h+L+1,cmp);
int tmp=L;top=0;
for(int i=1,l;i<=tmp;++i){
int u=h[i];
if(!top){
pa[u]=0;
st[++top]=u;
continue;
}
int w=lca(st[top],u);
//当前点和栈顶的LCA
while(dep[st[top]]>dep[w]){
if(dep[st[top-1]]<dep[w])pa[st[top]]=w;//如果下一个点的儿子是w,那么当前点的父亲就是w(维护链,按深度判断)
top--;//弹出
}
if(w!=st[top]){
h[++L]=w;
pa[w]=st[top];
st[++top]=w;
//如果w未进过栈,则其父亲是当前栈顶,h将其记录下,进栈
}
pa[u]=w,st[++top]=u;//u进栈,其父亲是上一个栈顶(w此时必然是栈顶)
}
sort(h+1,h+L+1,cmp);//现在h里面存了所有虚树上的点,按深度排序
}
void DP(){
for(int i=1;i<=L;++i)ans[h[i]]=0;//初始化
for(int i=L;i>=2;--i){//从深度最大的开始dp,第一个点一定是根 不dp
int u=h[i];
if(vis[u])ans[pa[u]]+=1ll*val[u];//基本dp不解释
else ans[pa[u]]+=min(1ll*val[u],ans[u]);
}
}
long long solve(){
for(int i=2;i<=L;++i)val[h[i]]=dis(h[i],pa[h[i]]);//预处理dis 倍增处理掉
DP();return ans[1];
}
int main(){
n=read();
for(int i=1;i<n;++i){
int x=read(),y=read(),z=read();
add(x,y,z);add(y,x,z);
}
m=read();dfs(1,0);//处理字典序 深度 边权最小值 LCA等信息
while(m--){
L=read()+1;h[1]=1;TM=L-1;//强制第一个点是1
for(int j=2;j<=L;++j)h[j]=read(),vis[h[j]]=1,yy[j-1]=h[j];
Build();printf("%lld\n",solve());
for(int i=1;i<=TM;++i)vis[yy[i]]=0;
}
return 0;
}

【学习笔记/题解】虚树/[SDOI2011]消耗战的更多相关文章

  1. 【学习笔记】虚树复习记(BZOJ2286 SDOI2011 消耗战)

    想写战略游戏却想不起来虚树T^T 所以就有了这篇复习记QwQ ——简介!—— 我们在处理树上问题的时候,dfs是一个常用手段,但是我们发现,如果一棵树上只有一部分关键点,每次dfs需要访问好多不是关键 ...

  2. 虚树------sdoi2011<消耗战>

    卡着时间过得,大概是因为全用了ll,时间涨了一倍吧?? 懒得改了,第一道虚树还是思路比较重要 下面这段文字是复制来的: 给出一棵树. 每次询问选择一些点,求一些东西.这些东西的特点是,许多未选择的点可 ...

  3. 【学习笔记】线段树—扫描线补充 (IC_QQQ)

    [学习笔记]线段树-扫描线补充 (IC_QQQ) (感谢 \(IC\)_\(QQQ\) 大佬授以本内容的著作权.此人超然于世外,仅有 \(Luogu\) 账号 尚可膜拜) [学习笔记]线段树详解(全) ...

  4. [模板] 虚树 && bzoj2286-[Sdoi2011]消耗战

    简介 虚树可以解决一些关于树上一部分节点的问题. 对于一棵树 \(T\) 的一个子集 \(S\), 可以在 \(O(|S| \log |S|)\) 的时间复杂度内求出 \(S\) 的虚树. 虚树包括根 ...

  5. 算法复习——虚树(消耗战bzoj2286)

    题目: Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战 ...

  6. 【学习笔记 边分树】【uoj400】【CTSC2018】暴力写挂

    题目 描述 ​ 有两棵树\(T\)和\(T'\),节点个数都为\(n\),根节点都为\(1\)号节点; ​ 求两两点之间 $$ \begin{align} depth(x) + depth(y) - ...

  7. 【学习笔记】动态树Link-Cut-Tree

    这是两个月前写的,看能不能搬运过来…… 动态树是一类维护森林连通性的问题(已纠正,感谢ZQC巨佬),目前最(wo)常(zhi)见(hui)的动态树就是LCT(Link-Cut-Tree),然而LCT似 ...

  8. SQL学习笔记之B+树的几点总结

    本文主要以列表形式将B+树的特点以及注意点等列出来,主要参考<算法导论>.维基百科.各大博客的内容,结合自己的理解写的,如内容有不当之处,请各位雅正. 0x00 前言 B树是为磁盘或其他直 ...

  9. 【loj2568】【APIO2016】【学习笔记 左偏树】烟花表演

    题目 一棵树,\(n\)个非叶子节点,编号为\(1-n\),\(m\)个叶子节点,编号为\(n+1-n+m\) 每条边有边权,修改边权的代价为\(|a-b|\) ; 定义一个叶子的距离为到1(根节点) ...

随机推荐

  1. Android Studio出现:Your project path contains non-ASCII 错误代码解决方法

    导入Project的出现: Error:(1, 0) Your project path contains non-ASCII characters. This will most likely ca ...

  2. 总结回顾js arr的常见方法以及相关的使用场景(一)

    Array对象方法 1.arr.concat() 连接两个或更多的数组,并返回结果. 连接数组中的值 连接两个数组 连接三个数组 2.arr.join() 把数组的所有元素放入一个字符串.元素通过指定 ...

  3. springboot AOP实战

    目录 AOP实战 maven依赖 定义切面 采用扫描类的方式 采用注解的方式 通知 前置通知 后置通知 返回通知 异常通知 环绕通知 JoinPoint 获取切点处的注解 git AOP实战 mave ...

  4. NGINX 命令 重启 WINDOWS

    最近系统更新比较频繁,web系统老是上新,因此在nginx这边经常需要重启或者刷新,做了一个批命令供参考. 1.鼠标右键-新建-一个.TXT文本文档:在里面输入NGINX重启的命令. 2.输入NGIN ...

  5. HDU-4417-Super Mario(主席树解法)

    Mario is world-famous plumber. His “burly” figure and amazing jumping ability reminded in our memory ...

  6. 微信小程序(1)

    微信小程序 什么是微信小程序? 微信小程序优点 跨平台 打开速度比h5快 不用下载 开发目录结构介绍 1. 小程序Pages目录说明 2. 工具文件夹 3. 全局文件 用法1 全局APP.json文件 ...

  7. SpringBoot2 引入 Aop

    一步小心就掉进坑里面了:SpringBoot2 引入 Aop 不生效 SpringBoot2.1.3版本 首先,引入依赖 <!--面向切面--> <dependency> &l ...

  8. linux 增加新用户无法使用sudo命令解决办法

    昨天一不小心把自己的系统搞崩了,也没有快照,没法进行还原操作,所以只能重装系统解决了,装完系统以后一切正常,当我新增了一个用户,使用sudo命令切换到root用户时,发现怎么都切换不过去,经过百度发现 ...

  9. leetcode刷题-37解数独

    题目 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次.数字 1-9 在每一列只能出现一次.数字 1-9 在每一个以粗实线分隔的 3x ...

  10. Flash 0day(CVE-2018-4878)复现过程

    一.前言介绍 2018年2月1号,Adobe官方发布安全通报(APSA18-01),声明Adobe Flash 28.0.0.137及其之前的版本,存在高危漏洞(CVE-2018-4878). 从Ad ...