[HEOI2014]大工程

III.[HEOI2014]大工程

仍然建出虚树。

我们考虑设szx表示x子树中实点(即原本点集中的点)的数量,再设fx表示xx子树中某个实点的最长路径,gx则表示最短路径。

我们先考虑求minmax的部分。

对于一个实点,它初始值fx=gx=0(有一条到自己的路径);对于一条虚点,它初始fx=,gx=

我们枚举它的儿子y。设z表示这条边的边权(实际上就是y的深度减去x的深度),则我们可以用fx+z+fy拼成一条最长路径,gx+z+gy拼成一条最短路径。然后更新fg即可,就跟正常树中求直径的做法没啥区别。

我们再考虑求和的地方。

显然,对于一条边(x,y)(这里令xy在虚树上的父亲),它总共会被经过szy×(qszy)次,其中q是点集大小。这也很好理解——对于y侧树中每个实点,它都会与x侧树中每个实点有一条边。则只需要用次数乘上边权,再求和,便是答案。

这题我担心nlogn空间的倍增或者ST表可能会MLE,就使用了树剖。但是总复杂度仍然是O(nlogn)的。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,fa[1001000],dep[1001000],dfn[1001000],top[1001000];
namespace real{
	int sz[1001000],son[1001000],tot;
	vector<int>v[1001000];
	void dfs1(int x,int Fa){
		fa[x]=Fa,dep[x]=dep[Fa]+1,dfn[x]=++tot,sz[x]=1;
		for(auto y:v[x]){
			if(y==fa[x])continue;
			dfs1(y,x),sz[x]+=sz[y];
			if(sz[son[x]]<sz[y])son[x]=y;
		}
	}
	void dfs2(int x){
		if(son[x])top[son[x]]=top[x],dfs2(son[x]);
		for(auto y:v[x])if(y!=fa[x]&&y!=son[x])top[y]=y,dfs2(y);
	}
}
int LCA(int x,int y){
	while(top[x]!=top[y])dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]];
	return dep[x]<dep[y]?x:y;
}
namespace imag{
	int q,stk[1001000],tp,a[1001000],sz[1001000],mn,mx,f[1001000],g[1001000];
	ll res;
	vector<int>v[1001000];
	bool cmp(int x,int y){
		return dfn[x]<dfn[y];
	}
	void ins(int x){
		sz[x]=1;
		if(!tp){stk[++tp]=x;return;}
		int lca=LCA(x,stk[tp]);
		while(tp>=2&&dep[lca]<dep[stk[tp-1]])v[stk[tp-1]].push_back(stk[tp]),tp--;
		if(tp&&dep[lca]<dep[stk[tp]])v[lca].push_back(stk[tp--]);
		if(!tp||stk[tp]!=lca)stk[++tp]=lca;
		stk[++tp]=x;
	}
	void fin(){
		while(tp>=2)v[stk[tp-1]].push_back(stk[tp]),tp--;
		tp--;
	}
	void dfs0(int x){
		if(sz[x])f[x]=g[x]=0;
		else f[x]=-0x3f3f3f3f,g[x]=0x3f3f3f3f;
		for(auto y:v[x]){
			dfs0(y);
			mx=max(mx,f[x]+(dep[y]-dep[x])+f[y]),f[x]=max(f[x],(dep[y]-dep[x])+f[y]);
			mn=min(mn,g[x]+(dep[y]-dep[x])+g[y]),g[x]=min(g[x],(dep[y]-dep[x])+g[y]);
		}
	}
	void dfs1(int x){
		for(auto y:v[x])dfs1(y),res+=1ll*(dep[y]-dep[x])*sz[y]*(q-sz[y]),sz[x]+=sz[y];
	}
	void dfs2(int x){
		sz[x]=0;
		for(auto y:v[x])dfs2(y);
		v[x].clear();
	}
	void work(){
		res=0,mn=0x3f3f3f3f,mx=-0x3f3f3f3f,scanf("%d",&q);
		for(int i=1;i<=q;i++)scanf("%d",&a[i]);
		sort(a+1,a+q+1,cmp);
		if(a[1]!=1)stk[++tp]=1;
		for(int i=1;i<=q;i++)ins(a[i]); 
		fin();
		dfs0(1);
		dfs1(1);
		printf("%lld %lld %lld\n",res,mn,mx);
		dfs2(1);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),real::v[x].push_back(y),real::v[y].push_back(x);
	real::dfs1(1,0),top[1]=1,real::dfs2(1);
	scanf("%d",&m);
	while(m--)imag::work();
	return 0;
}

posted @   Troverld  阅读(45)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示