题面

国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。

我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。

在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径。

现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道。

现在对于每个计划,我们想知道:

1.这些新通道的代价和

2.这些新通道中代价最小的是多少

3.这些新通道中代价最大的是多少

分析

显然需要建虚树,我们考虑如何在虚树上DP(注意:以下定义的点和子树都是虚树上的)

设sz[x]表示x的子树内有多少个询问点,sum[x]表示x子树内的路径长度和,dmin[x]表示x子树内到x的最小路径长度,dmax[x]表示 x子树内到x的最大路径长度

对于x的子节点y,我们可以写出如下的状态转移方程

sum[x]+=sum[y]+(k-sz[y]) · sz[y] ·dist(x,y)

x的子树内的路径分为两种:一种是在y的子树内部的路径,一种是从y的子树内的sz[y]个节点到子树外(k-sz[y])个节点)

dmin[x]=min(dmin[x],dmin[y]+dist(x,y));

dmax[x]=max(dmax[x],dmax[y]+dist(x,y));

long long siz[maxn];//siz[x],x子树内询问点的个数
long long sum[maxn],dmin[maxn],dmax[maxn];
//sum[x] x子树内的路径长度和
//dmin[x] x子树内到x的最小路径长度
//dmax[x] x子树内到x的最大路径长度
long long ans_sum,ans_min,ans_max;
void dfs2(int x,int fa){
siz[x]=is_q[x];
dmax[x]=0;
dmin[x]=INF;
sum[x]=0;
for(int i=T2.head[x];i;i=T2.E[i].next){
int y=T2.E[i].to;
long long len=deep[y]-deep[x];
if(y!=fa){
dfs2(y,x);
sum[x]+=sum[y]+siz[y]*(k-siz[y])*len;
//y子树内部路径 + y子树到外面其他询问点的路径
siz[x]+=siz[y];
ans_min=min(ans_min,dmin[x]+dmin[y]+len);
ans_max=max(ans_max,dmax[x]+dmax[y]+len);
dmin[x]=min(dmin[x],dmin[y]+len);
dmax[x]=max(dmax[x],dmax[y]+len);
}
}
if(is_q[x]){
ans_min=min(ans_min,dmin[x]);
ans_max=max(ans_max,dmax[x]);
dmin[x]=0;//当前最小值为0,这样祖先节点就可以用它来更新最小值(最小路径可能是祖先~x)
}
T2.head[x]=0;
}

注意,此题需要卡常

可以预处理出2的i次方,然后在lca里面优化倍增,详情见代码

代码

//http://119.29.55.79/problem/276
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 1000005
#define maxlog 32
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
inline void qread(int &x){
x=0;
int sign=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
inline void qprint(long long x){
if(x<0){
putchar('-');
qprint(-x);
}else if(x==0){
putchar('0');
return;
}else{
if(x/10>0) qprint(x/10);
putchar('0'+x%10);
}
} int n,m;
struct graph{
struct edge{
int from;
int to;
int next;
}E[maxn<<1];
int head[maxn];
int sz;
void add_edge(int u,int v){
sz++;
E[sz].from=u;
E[sz].to=v;
E[sz].next=head[u];
head[u]=sz;
sz++;
E[sz].from=v;
E[sz].to=u;
E[sz].next=head[v];
head[v]=sz;
}
graph(){
sz=1;
}
}T1,T2; int log2n;
int tim=0;
int dfn[maxn];
int deep[maxn];
int bin[maxn];//卡常用
int anc[maxn][maxlog];
void dfs1(int x,int fa){
dfn[x]=++tim;
deep[x]=deep[fa]+1;
anc[x][0]=fa;
for(int i=1;bin[i]<=deep[x];i++)anc[x][i]=anc[anc[x][i-1]][i-1];
for(int i=T1.head[x];i;i=T1.E[i].next){
int y=T1.E[i].to;
if(y!=fa){
dfs1(y,x);
}
}
}
int lca(int x,int y){
if(deep[x]<deep[y]) swap(x,y);
int t=deep[x]-deep[y];
for(int i=0;bin[i]<=t;i++){
if(bin[i]&t){
x=anc[x][i];
}
}
if(x==y) return x;
for(int i=log2n;i>=0;i--){
if(anc[x][i]!=anc[y][i]){
x=anc[x][i];
y=anc[y][i];
}
}
return anc[x][0];
} int k;
int top=0;
int s[maxn];
int in[maxn];
int cmp(int x,int y){
return dfn[x]<dfn[y];
}
int is_q[maxn];//是否是询问点
void insert(int x){
if(top<=1){
s[++top]=x;
return;
}
int lc=lca(x,s[top]);
if(lc==s[top]){
s[++top]=x;
return;
}
while(top>1&&deep[s[top-1]]>=deep[lc]){
T2.add_edge(s[top-1],s[top]);
// printf("vtree : %d->%d\n",s[top-1],s[top]);
top--;
}
if(s[top]!=lc){
T2.add_edge(lc,s[top]);
// printf("vtree : %d->%d\n",lc,s[top]);
}
s[top]=lc;
s[++top]=x;
} long long siz[maxn];//siz[x],x子树内询问点的个数
long long sum[maxn],dmin[maxn],dmax[maxn];
//sum[x] x子树内的路径长度和
//dmin[x] x子树内到x的最小路径长度
//dmax[x] x子树内到x的最大路径长度
long long ans_sum,ans_min,ans_max;
void dfs2(int x,int fa){
siz[x]=is_q[x];
dmax[x]=0;
dmin[x]=INF;
sum[x]=0;
for(int i=T2.head[x];i;i=T2.E[i].next){
int y=T2.E[i].to;
long long len=deep[y]-deep[x];
if(y!=fa){
dfs2(y,x);
sum[x]+=sum[y]+siz[y]*(k-siz[y])*len;
//y子树内部路径 + y子树到外面其他询问点的路径
siz[x]+=siz[y];
ans_min=min(ans_min,dmin[x]+dmin[y]+len);
ans_max=max(ans_max,dmax[x]+dmax[y]+len);
dmin[x]=min(dmin[x],dmin[y]+len);
dmax[x]=max(dmax[x],dmax[y]+len);
}
}
if(is_q[x]){
ans_min=min(ans_min,dmin[x]);
ans_max=max(ans_max,dmax[x]);
dmin[x]=0;//当前最小值为0,这样祖先节点就可以用它来更新最小值(最小路径可能是祖先~x)
}
T2.head[x]=0;
} void solve(){
sort(in+1,in+1+k,cmp);
for(int i=1;i<=k;i++) is_q[in[i]]=1;
int root=lca(in[1],in[2]);
for(int i=3;i<=k;i++){
root=lca(root,in[i]);
}
top=0;
s[++top]=root;
for(int i=1;i<=k;i++){
if(in[i]!=root)insert(in[i]);
}
while(top>1){
T2.add_edge(s[top-1],s[top]);
// printf("vtree : %d->%d\n",s[top-1],s[top]);
top--;
}
ans_sum=0;
ans_max=0;
ans_min=INF;
dfs2(root,0);
ans_sum=sum[root];
for(int i=1;i<=k;i++) is_q[in[i]]=0;
T2.sz=1;
} int main(){
#ifdef FILE_IO
freopen("tree9.in","r",stdin);
freopen("tree9.ans","w",stdout);
#endif
int u,v;
qread(n);
log2n=log2(n)+1;
bin[0]=1;
for(int i=1;i<=log2n;i++) bin[i]=bin[i-1]*2;
for(int i=1;i<n;i++){
qread(u);
qread(v);
T1.add_edge(u,v);
}
dfs1(1,0);
qread(m);
for(int i=1;i<=m;i++){
qread(k);
for(int j=1;j<=k;j++){
qread(in[j]);
}
solve();
qprint(ans_sum);putchar(' ');
qprint(ans_min);putchar(' ');
qprint(ans_max);putchar('\n');
}
}

BZOJ 3611 大工程 (虚树)的更多相关文章

  1. bzoj 3611: [Heoi2014]大工程 虚树

    题目: 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 ...

  2. bzoj 3611(洛谷 4103) [Heoi2014]大工程——虚树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3611 https://www.luogu.org/problemnew/show/P4103 ...

  3. BZOJ.3611.[HEOI2014]大工程(虚树 树形DP)

    题目链接 要求的和.最大值.最小值好像都可以通过O(n)的树形DP做,总询问点数<=2n. 于是建虚树就可以了.具体DP见DP()函数,维护三个值sum[],mx[],mn[]. sum[]要开 ...

  4. BZOJ 3611 [Heoi2014]大工程 ——虚树

    虚树第二题.... 同BZOJ2286 #include <map> #include <cmath> #include <queue> #include < ...

  5. luogu P4103 [HEOI2014]大工程 虚树 + 树形 DP

    Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道.  我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上.  在 2 个国家 a,b 之间建一条新通 ...

  6. 【HEOI2014】大工程<虚树>

    虚树 我们每天都用心思索着,这究竟是为了什么呢?我想我也不知道,只是觉得如果人不思考问题就很无聊. 我觉得虚树不是什么数据结构,就是一种技巧或者工具.它能把树中\(k\)个关键点以\(O(klogk) ...

  7. 洛谷P4103 [HEOI2014]大工程(虚树 树形dp)

    题意 链接 Sol 虚树. 首先建出虚树,然后直接树形dp就行了. 最大最小值直接维护子树内到该节点的最大值,然后合并两棵子树的时候更新一下答案. 任意两点的路径和可以考虑每条边两边的贡献,\(d[x ...

  8. [HEOI2014][bzoj3611] 大工程 [虚树+dp]

    题面: 传送门 思路: 又是一道虚树入门级的题目,但是这道题的实际难点在于dp 首先,这道题是可以点分治做的,而且因为6s时限随便浪,所以写点分治也不是不可以 但是,dp因为$O\left(n\rig ...

  9. bzoj 3611[Heoi2014]大工程 虚树+dp

    题意: 给一棵树 每次选 k 个关键点,然后在它们两两之间 新建 C(k,2)条 新通道. 求: 1.这些新通道的代价和 2.这些新通道中代价最小的是多少 3.这些新通道中代价最大的是多少 分析:较常 ...

随机推荐

  1. DevExpress Winform使用单例运行程序方法和非DevExpress使用Mutex实现程序单实例运行且运行则激活窗体的方法

    原文:DevExpress Winform使用单例运行程序方法和非DevExpress使用Mutex实现程序单实例运行且运行则激活窗体的方法 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA ...

  2. bzoj3097 Hash Killer I

    Hash Killer I Time Limit: 5 Sec Memory Limit: 128 MBSec Special Judge Description 这天天气不错,hzhwcmhf神犇给 ...

  3. 如何查找django安装路径

    需要找到django的安装路径,官方说的那个方法不好用,国内搜索都是都不到的,后来谷歌搜到了很简单 import django django 这样就可以找django的安装路径了,真心不懂为什么国内都 ...

  4. JS继承——原型链

    许多OO语言支持两种继承:接口继承和实现继承.ECMAScript只支持实现继承,且继承实现主要依赖原型链实现. 原型链 基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法. 构造函数.原 ...

  5. 下载了包在node_modules中,但没有在package.json中保存该包信息。

    发现安装了包,但没有在package.json中保存该包信息,而且没有创建package-lock.json. 经过测试,发现是使用cnpm的原因,使用npm安装不会出现这样的问题,(与cnpm版本无 ...

  6. 机器学习-线性回归补充-R^

    线性回归算法在选自变量会遇到两个问题:一是去掉多重共线性干扰,二是选择最优自变量组合. 线性回归步骤 1.选择自变量 注意点 去掉多重共线性干扰,选择最优自变量组合.这里需要理解决定系数:R^.它是理 ...

  7. linux安装jdk环境(多种方式)

    通过tar.gz压缩包安装 此方法适用于绝大部分的linux系统 1.先下载tar.gz的压缩包,这里使用官网下载. 进入: http://www.oracle.com/technetwork/jav ...

  8. spring整合消息队列rabbitmq

    ps:本文只是简单一个整合介绍,属于抛砖引玉,具体实现还需大家深入研究哈.. 1.首先是生产者配置 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...

  9. Linux的cat命令详解

    The cat command reads one or more files and copies them to standard output 也就是说,cat命令读取文件,并显示在 stand ...

  10. 图与例解读Async/Await

    JavaScript ES7的async/await语法让异步promise操作起来更方便.如果你需要从多个数据库或者接口按顺序异步获取数据,你可能最终写出一坨纠缠不清的promise与回调.然而使用 ...