题面:

传送门

思路:

一道虚树的好题,是很多虚树博客的入门题目

但是我认为这道题目出的难点和亮点不在于虚树,而在于建出虚树以后dp的思路与实现

下文中为方便描述,用势力范围来表示一个“议事处”管辖的节点集合

首先,看完题目以后一个直观的感受,就是我去考虑两个询问点之间,哪些点属于谁的势力范围,然后这样

这是个类似暴力的东西

那树上路径的中点怎么求?可以lca求出距离以后倍增

但是现在问题来了,虚树上的节点有的是询问点,有的只是询问点的lca,并不参与询问,怎么解决这个问题呢?

而且在那个初始想法中,有一些路径是势必会重复的,又怎么解决?

一个直观的想法就是把一堆路径询问简化掉

我们发现一个点终究只能属于一个询问点的势力范围,那么一条边上的节点同理

所以我们可以把重复经过同一条边的询问拆到每条虚树边上,然后对于每条虚树边来考虑解题

显然,虚树边分为两种:两边的虚树点被同一个节点控制的,或者两端被不同节点控制的

对于两端被同一个询问点控制的边,显然这条虚树边上的所有的点的所有子树都被这个点控制

另一种情况则是在中间有一个分界线,界线一边的点与它们的子树被那一边的端点的“主人”控制

那么也就是说,我们只需要处理出虚树上的每一个节点被哪个询问点控制,然后对于上面的两种情况分别考虑、统计答案就行了

那么怎么处理这个控制关系呢?

我们做两次dfs

第一次,我们求出某个虚树节点的所有儿子中离它最近的控制点

第二次,则用父亲的控制点(和儿子不在同一个子树中)来更新儿子的控制点

dp的时候则是依然用倍增来实现跳到分界点上

两个点之间的距离则可以用lca实现

大概就是这样了,更具体的内容可以参考代码

Code:

 #include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
#define inf 1e9
using namespace std;
inline int read(){
int re=,flag=;char ch=getchar();
while(ch>''||ch<''){
if(ch=='-') flag=-;
ch=getchar();
}
while(ch>=''&&ch<='') re=(re<<)+(re<<)+ch-'',ch=getchar();
return re*flag;
}
int n,dep[],siz[],st[][],dfn[],clk;
struct graph{
int first[],cnt;
struct edge{
int to,next;
}a[];
inline void add(int u,int v){
if(u==v) return;
a[++cnt]=(edge){v,first[u]};first[u]=cnt;
}
graph(){
memset(first,-,sizeof(first));cnt=;
}
void init(){cnt=;}
}G,g;
void dfs(int u,int f){
// cout<<"begin dfs "<<u<<endl;
int i,v;
dep[u]=dep[f]+;st[u][]=f;dfn[u]=++clk;siz[u]=;
for(i=G.first[u];~i;i=G.a[i].next){
v=G.a[i].to;
if(v==f) continue;
dfs(v,u);siz[u]+=siz[v];
}
// cout<<"end of dfs "<<u<<ends<<dep[u]<<ends<<siz[u]<<endl;
return;
}
void ST(){
int i,j;
for(j=;j<=;j++) for(i=;i<=n;i++) st[i][j]=st[st[i][j-]][j-];
}
int lca(int l,int r){
if(dep[l]>dep[r]) swap(l,r);
int i;
for(i=;i>=;i--) if(dep[st[r][i]]>=dep[l]) r=st[r][i];
if(l==r) return l;
for(i=;i>=;i--)
if(st[r][i]!=st[l][i]){
r=st[r][i];
l=st[l][i];
}
return st[l][];
}
int q[],ans[],num[],s[],top;
bool vis[];
bool cmp1(int l,int r){
return dfn[l]<dfn[r];
}
bool cmp2(int l,int r){
return num[l]<num[r];
}
int minn[],belong[],sur[];
int dis(int l,int r){return dep[l]+dep[r]-*dep[lca(l,r)];}
void dfs1(int u){
int i,v,d1,d2;belong[u]=(vis[u]?u:);sur[u]=siz[u];
for(i=g.first[u];~i;i=g.a[i].next){
v=g.a[i].to;dfs1(v);
d1=dep[belong[v]]-dep[u];d2=(belong[u]?dep[belong[u]]-dep[u]:inf);
if(d1<d2||(d1==d2&&belong[v]<belong[u])) belong[u]=belong[v];
}
}
void dfs2(int u){
int i,v,d1,d2;
for(i=g.first[u];~i;i=g.a[i].next){
v=g.a[i].to;d1=dis(belong[u],v);d2=dis(belong[v],v);
if(d1<d2||(d1==d2&&belong[u]<belong[v])) belong[v]=belong[u];
dfs2(v);
}
}
void dp(int u){
// cout<<"enter dp "<<u<<ends<<belong[u]<<endl;
int i,v,d1,d2,mid,tv,j,tmp;
for(i=g.first[u];~i;i=g.a[i].next){
v=g.a[i].to;g.first[u]=g.a[i].next;mid=tv=v;
// cout<<"going to "<<v<<endl;
dp(v);
for(j=;j>=;j--) if(dep[st[tv][j]]>dep[u]) tv=st[tv][j];
sur[u]-=siz[tv];
// cout<<"edge up to "<<tv<<endl;
if(belong[u]==belong[v]){
ans[belong[u]]+=siz[tv]-siz[v];continue;
}
for(j=;j>=;j--){
tmp=st[mid][j];if(dep[tmp]<=dep[u]) continue;
d1=dis(tmp,belong[v]);d2=dis(tmp,belong[u]);
if(d1<d2||(d1==d2&&belong[v]<belong[u])) mid=tmp;
}
// cout<<"********diff edge "<<mid<<endl;
ans[belong[u]]+=siz[tv]-siz[mid];
ans[belong[v]]+=siz[mid]-siz[v];
}
// cout<<"end of dp "<<u<<ends<<sur[u]<<endl;
ans[belong[u]]+=sur[u];
}
void solve(){
// cout<<"*************************enter solve\n";
int m=read(),i,grand;
for(i=;i<=m;i++) q[i]=read(),num[q[i]]=i,vis[q[i]]=;;
sort(q+,q+m+,cmp1);top=;g.init();s[++top]=;
for(i=;i<=m;i++){
// if(!top){s[++top]=q[i];continue;}
grand=lca(s[top],q[i]);
while(){
if(dep[s[top-]]<=dep[grand]){
g.add(grand,s[top--]);
if(s[top]!=grand) s[++top]=grand;
break;
}
g.add(s[top-],s[top]);top--;
}
if(s[top]!=q[i]) s[++top]=q[i];
}
while(--top>=) g.add(s[top],s[top+]);
dfs1(s[]);dfs2(s[]);dp(s[]);
sort(q+,q+m+,cmp2);
// for(i=1;i<=m;i++) cout<<q[i]<<ends;cout<<endl;
for(i=;i<=m;i++) vis[q[i]]=,num[q[i]]=inf,printf("%d ",ans[q[i]]),ans[q[i]]=;
puts("");
}
int main(){
// cout<<"begin\n";
int i,t1,t2;
n=read();memset(num,0x3f,sizeof(num));
// cout<<"input n "<<n<<endl;
for(i=;i<n;i++){
t1=read();t2=read();
G.add(t1,t2);G.add(t2,t1);
}
// cout<<"input of tree end\n";
dfs(,);ST();
int Q=read();
for(i=;i<=Q;i++) solve();
}

[HNOI2014][bzoj3572] 世界树 [虚树+dp]的更多相关文章

  1. BZOJ 3572 [HNOI2014]世界树 (虚树+DP)

    题面:BZOJ传送门 洛谷传送门 题目大意:略 细节贼多的虚树$DP$ 先考虑只有一次询问的情况 一个节点$x$可能被它子树内的一个到x距离最小的特殊点管辖,还可能被管辖fa[x]的特殊点管辖 跑两次 ...

  2. bzoj 3572世界树 虚树+dp

    题目大意: 给一棵树,每次给出一些关键点,对于树上每个点,被离它最近的关键点(距离相同被标号最小的)控制 求每个关键点控制多少个点 分析: 虚树+dp dp过程如下: 第一次dp,递归求出每个点子树中 ...

  3. bzoj3572[Hnoi2014] 世界树 虚树+dp+倍增

    [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1921  Solved: 1019[Submit][Status][Dis ...

  4. BZOJ 3572: [Hnoi2014]世界树 [虚树 DP 倍增]

    传送门 题意: 一棵树,多次询问,给出$m$个点,求有几个点到给定点最近 写了一晚上... 当然要建虚树了,但是怎么$DP$啊 大爷题解传送门 我们先求出到虚树上某个点最近的关键点 然后枚举所有的边$ ...

  5. 洛谷 P3233 [HNOI2014]世界树(虚树+dp)

    题面 luogu 题解 数据范围已经告诉我们是虚树了,考虑如何在虚树上面\(dp\) 以下摘自hzwer博客: 构建虚树以后两遍dp处理出虚树上每个点最近的议事处 然后枚举虚树上每一条边,考虑其对两端 ...

  6. 【BZOJ】3572: [Hnoi2014]世界树 虚树+倍增

    [题意]给定n个点的树,m次询问,每次给定ki个特殊点,一个点会被最近的特殊点控制,询问每个特殊点控制多少点.n,m,Σki<=300000. [算法]虚树+倍增 [题解]★参考:thy_asd ...

  7. bzoj 2286 [Sdoi2011]消耗战 虚树+dp

    题目大意:多次给出关键点,求切断边使所有关键点与1断开的最小费用 分析:每次造出虚树,dp[i]表示将i和i子树与父亲断开费用 对于父亲x,儿子y ①y为关键点:\(dp[x]\)+=\(dismn( ...

  8. 【BZOJ】2286: [Sdoi2011]消耗战 虚树+DP

    [题意]给定n个点的带边权树,每次询问给定ki个特殊点,求隔离点1和特殊点的最小代价.n<=250000,Σki<=500000. [算法]虚树+DP [题解]考虑普通树上的dp,设f[x ...

  9. [BZOJ5287][HNOI2018]毒瘤(虚树DP)

    暴力枚举非树边取值做DP可得75. 注意到每次枚举出一个容斥状态的时候,都要做大量重复操作. 建立虚树,预处理出虚树上两点间的转移系数.也可动态DP解决. 树上倍增.动态DP.虚树DP似乎是这种问题的 ...

随机推荐

  1. opencv与灰度图

    https://blog.csdn.net/qq_32211827/article/details/56854985 首先,灰度图可以是一个通道存成图片,也可以是3个通道存成图片,3个通道存成图片,其 ...

  2. C++手写快读详解(快速读入数字)

    众所周知,C++里是自带读入的(这不废话吗) 例如: int a; cin>>a; 这样的读入理解简单,适合初学者,但是非常慢. 再例如: int a; scanf("%d&qu ...

  3. Activiti学习记录(三)

    1.流程变量 1.1 流程图 流程变量在整个工作流中扮演很重要的作用.例如:请假流程中有请假天数.请假原因等一些参数都为流程变量的范围.流程变量的作用域范围是只对应一个流程实例.也就是说各个流程实例的 ...

  4. java定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积

    需求如下:(1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积. (2)定义一个类PassObject,在类中定义一个方法pri ...

  5. Java 对数组的筛选

    在Java里面 一般对一个数组进行筛选,去剔除一些元素,一般做法是用临时数组来存储,把符合条件的元素加入到新数组中,虽然数组有移除的方法但是 是线程不安全的: 而用迭代器Iterator,可以在遍历的 ...

  6. 网站动态加载JS脚本

    Demo_1 function loadJS(url, fn) { var ss = document.getElementsByName('script'), loaded = false; for ...

  7. python3.7 json模块

    #!/usr/bin/env python __author__ = "lrtao2010" #python3.7 json模块 ''' 要在不同的编程语言之间传递对象,就必须把对 ...

  8. python3爬虫之Urllib库(二)

    在上一篇文章中,我们大概讲了一下urllib库中最重要的两个请求方法:urlopen()  和  Request() 但是仅仅凭借那两个方法无法执行一些更高级的请求,如Cookies处理,代理设置等等 ...

  9. Python虚拟机类机制之descriptor(三)

    从slot到descriptor 在Python虚拟机类机制之填充tp_dict(二)这一章的末尾,我们介绍了slot,slot包含了很多关于一个操作的信息,但是很可惜,在tp_dict中,与__ge ...

  10. java.util.ArrayList与java.util.Arrays$ArrayList区别

    本博客转载自:https://blog.csdn.net/maywehe/article/details/52553954 写demo的时候,为了避免用list.add方法,特意写了个数组然后转换成l ...