【XSY1977】【BZOJ3611】【HEOI2014】大工程(虚树+dp)

Description

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

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

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

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

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

1.这些新通道的代价和

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

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


Input

第一行 n 表示点数。
接下来 n1 行,每行两个数 a,b 表示 ab 之间有一条边。点从 1 开始标号。
接下来一行 q 表示计划数。
对每个计划有 2 行,第一行 k 表示这个计划选中了几个点。
第二行用空格隔开的 k 个互不相同的数表示选了哪 k 个点。


Output

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


SampleInput

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


SampleOutput

3 3 3
6 6 6
1 1 1
2 2 2
2 2 2


HINT

n1000000
q50000,ki2×n


Source

 练习题 树8-5-虚树

思路

首先我们考虑dp

我们设三个数组sum[u],mn[u],mx[u]

分别表示,对于以u为根的子树中的代价总和,最小值和最大值

u到当前遍历到的儿子v的代价为cost

那么我们轻易得出dp方程:

{mn[u]=min(mn[u],mn[v]+cost)mx[u]=max(mx[u],mx[v]+cost)sum[u]+=sum[v]+siz[v]×cost

同时,对于答案的更新就很显然了:

{ans1+=siz[u]×(sum[v]+costsiz[v])+siz[v]×sum[u]ans2=min(ans2,mn[u]+mn[v]+cost)ans3=max(ans3,mx[u]+mx[v]+cost)

以上的如果不懂可以自己手画图来推下,容易理解

但是,我们来瞅一眼时间复杂度:在这种情况下,时间复杂度是O(qn)

是的你没看错,绝对会爆


正当我们一筹莫展的时候,仔细看一遍题面会对我们有极大帮助

我们一眼看到 ki2×n ,果断虚树


关于不会虚树的小白,请参考自为风月马前卒大佬的博客

学会建虚树后,我们直接开始在虚树上dp就好了

注意:

  1. 要开 longlong
  2. 在虚树上 dp 时,不要直接使用原树中处理出的siz,毕竟虚树的siz跟原树中的siz还是有很大区别的
  3. 因为在虚树中dp时,对于关键点和非关键点的处理是不同的:
    如果是关键点, 那么mn[u]=mx[u]=0,sz[u]=1
    如果不是,那么mn[u]=INF,mx[u]=INF,sz[u]=0

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
const long long INF=0x7ffffffffff;
int n,k,q,cnt=0,tot=0,ind=0;
int to[N<<1],nxt[N<<1],head[N],w[N<<1];
int p[N];
int fa[N];
int st[N];
int d[N];
int dfn[N];
int son[N];
int topp[N];
int siz[N];
bool vis[N];
void add(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}
void add2(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	w[cnt]=d[v]-d[u];
	head[u]=cnt;
}
void dfs1(int u)
{
	siz[u]=1;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[u])continue;
		d[v]=d[u]+1;
		fa[v]=u;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs2(int u,int tp)
{
	topp[u]=tp;
	dfn[u]=++ind;
	if(son[u])dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
int query_lca(int x,int y)
{
	while(topp[x]!=topp[y])
	{
		if(d[topp[x]]<d[topp[y]])swap(x,y);
		x=fa[topp[x]];
	}
	if(d[x]>d[y])return y;
	return x;
}
bool cmp(int x,int y)
{
	return dfn[x]<dfn[y];
}
void ins(int x)
{
	if(tot==1)
	{
		st[++tot]=x;
		return ;
	}
	int lca=query_lca(st[tot],x);
	while(tot>1&&dfn[st[tot-1]]>=dfn[lca])add2(st[tot-1],st[tot]),tot--;
	if(lca!=st[tot])add2(lca,st[tot]),st[tot]=lca;
	st[++tot]=x;
}
long long ans1,ans2,ans3;
long long sum[N],mn[N],mx[N];
int sz[N];
void dp(int u)
{
	sum[u]=0;
	if(vis[u])mn[u]=mx[u]=0,sz[u]=1;
	else mn[u]=INF,mx[u]=-INF,sz[u]=0;
	for(int i=head[u];i;i=nxt[i])
	{
		int cost=w[i],v=to[i];
		dp(v);
		ans2=min(ans2,mn[u]+mn[v]+cost);
		ans3=max(ans3,mx[u]+mx[v]+cost);
		mn[u]=min(mn[u],mn[v]+cost);
		mx[u]=max(mx[u],mx[v]+cost);
		ans1+=1ll*sz[u]*(sum[v]+cost*sz[v])+1ll*sz[v]*sum[u];
		sum[u]+=sum[v]+sz[v]*cost;
		sz[u]+=sz[v];
	}
	vis[u]=0,head[u]=0;
}
int main()
{
	scanf("%d",&n);
	int a,b;
	for(int i=1;i<n;i++)
	{
		scanf("%d %d",&a,&b);
		add(a,b),add(b,a);
	}
	d[1]=1;
	dfs1(1);
	dfs2(1,1);
	scanf("%d",&q);
	memset(head,0,sizeof(head));
	while(q--)
	{
		cnt=0;
		scanf("%d",&k);
		for(int i=1;i<=k;i++)scanf("%d",&p[i]),vis[p[i]]=1;
		sort(p+1,p+k+1,cmp);
		if(p[1]!=1)st[tot=1]=1;
		for(int i=1;i<=k;i++)ins(p[i]);
		while(tot)add2(st[tot-1],st[tot]),tot--;
		ans1=0ll,ans2=INF,ans3=-INF;
		dp(1);
		printf("%lld %lld %lld\n",ans1,ans2,ans3);
	}
	return 0;
}
/*
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
*/
posted @   ShuraEye  阅读(167)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示