[HEOI2014]大工程

题目描述

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

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

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

现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道。现在对于每个计划,我们想知道: 1.这些新通道的代价和 2.这些新通道中代价最小的是多少 3.这些新通道中代价最大的是多少

输入输出格式

输入格式:

第一行 n 表示点数。

接下来 n-1 行,每行两个数 a,b 表示 a 和 b 之间有一条边。点从 1 开始标号。

接下来一行 q 表示计划数。对每个计划有 2 行,第一行 k 表示这个计划选中了几个点。

第二行用空格隔开的 k 个互不相同的数表示选了哪 k 个点。

输出格式:

输出 q 行,每行三个数分别表示代价和,最小代价,最大代价。

输入输出样例

输入样例#1: 复制
10
2 1
3 2
4 1
5 2
6 4
7 5
8 6
9 7
10 9
5
2
5 4
2
10 4
2
5 2
2
6 1
2
6 1
输出样例#1: 复制
3 3 3
6 6 6
1 1 1
2 2 2 2 2 2

说明

对于第 1,2 个点: n<=10000

对于第 3,4,5 个点: n<=100000,交通网络构成一条链

对于第 6,7 个点: n<=100000

对于第 8,9,10 个点: n<=1000000

对于所有数据, q<=50000并且保证所有k之和<=2*n

看到k的和小于2*n,于是立刻想到建虚树

建出虚树后就dp

size[x]表示x的子树中关键点数

f[x]表示x子树中路径的贡献和

Min[x]表示x子树中离x距离最小的关键点的距离

Max[x]表示最大的距离

最大值和求和很简单

最大值总是要取到叶子节点,虚树中叶子节点总是关键点

答案取当前Max[x]+Max[v]+边权w,然后Max[x]=max(Max[x],Max[v]+d)

求和就考虑一条边的贡献

一条边的贡献次数显然是size[v]*(k-size[v])

所以f[x]+=f[v]+size[v]*(k-size[v])*w

求最小值的话,Min[x]初值正无穷

如果是关键点就直接取它的子树路径最小值,否则就是它的两个儿子的子树路径最小值相加

如果是关键点,更新答案后Min[x]要清0,作为接下来的端点

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long lol;
const int N=;
struct Node
{
int next,to;
}edge[N],edge2[N];
int inf=1e9;
int dep[N],fa[N][],dfn[N],cnt,bin[],head[N],head2[N],num,ed[N];
int size[N],vis[N],Max[N],Min[N],k,M,ans1,ans2,n,Lca,a[N],s[N],top;
lol f[N];
int gi()
{
char ch=getchar();
int x=;
while (ch<''||ch>'') ch=getchar();
while (ch>=''&&ch<='')
{
x=x*+ch-'';
ch=getchar();
}
return x;
}
bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
void add(int u,int v)
{
num++;
edge[num].next=head[u];
head[u]=num;
edge[num].to=v;
}
void add2(int u,int v)
{
if (u==v) return;
num++;
edge2[num].next=head2[u];
head2[u]=num;
edge2[num].to=v;
}
void dfs(int x,int pa)
{int i;
dep[x]=dep[pa]+;
dfn[x]=++cnt;
for (i=;bin[i]<=dep[x];i++)
fa[x][i]=fa[fa[x][i-]][i-];
for (i=head[x];i;i=edge[i].next)
{
int v=edge[i].to;
if (v==pa) continue;
fa[v][]=x;
dfs(v,x);
}
ed[x]=cnt;
}
int lca(int x,int y)
{int as,i;
if (dep[x]<dep[y]) swap(x,y);
for (i=;i>=;i--)
if (bin[i]<=dep[x]-dep[y])
x=fa[x][i];
if (x==y) return x;
for (i=;i>=;i--)
{
if (fa[x][i]!=fa[y][i])
{
x=fa[x][i];y=fa[y][i];
}
}
return fa[x][];
} void dp(int x)
{int i;
size[x]=vis[x];
Max[x]=;Min[x]=inf;f[x]=;
for (i=head2[x];i;i=edge2[i].next)
{
int v=edge2[i].to,d=dep[v]-dep[x];
dp(v);
size[x]+=size[v];
f[x]+=f[v]+1ll*size[v]*(k-size[v])*d;
ans1=min(ans1,Min[x]+Min[v]+d);Min[x]=min(Min[x],Min[v]+d);
ans2=max(ans2,Max[x]+Max[v]+d);Max[x]=max(Max[x],Max[v]+d);
}
if (vis[x]) ans1=min(ans1,Min[x]),ans2=max(ans2,Max[x]),Min[x]=;
}
int main()
{int i,u,v,j,q;
cin>>n;
bin[]=;
for (i=;i<=;i++)
bin[i]=bin[i-]*;
for (i=;i<=n-;i++)
{
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(,);
cin>>q;
while (q--)
{
k=gi();
M=k;
num=;ans1=inf;ans2=;
for (i=;i<=k;i++)
a[i]=gi(),vis[a[i]]=;
sort(a+,a+k+,cmp);
Lca=a[];
for (i=;i<=k;i++)
if (ed[a[i-]]<dfn[a[i]])
a[++M]=lca(a[i-],a[i]),Lca=lca(Lca,a[i]);
a[++M]=Lca;
sort(a+,a+M+,cmp);
M=unique(a+,a+M+)-a-;
s[++top]=a[];
for (i=;i<=M;i++)
{
while (top&&ed[s[top]]<dfn[a[i]]) top--;
add2(s[top],a[i]);
s[++top]=a[i];
}
dp(Lca);
printf("%lld %d %d\n",f[Lca],ans1,ans2);
for (i=;i<=M;i++)
vis[a[i]]=head2[a[i]]=;
}
}
上一篇:CentOS 7 中英文桌面安装步骤详细图解


下一篇:bzoj 3611 [Heoi2014]大工程(虚树+DP)