2918. 大工程

题目链接

2918. 大工程

题目描述

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

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

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

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

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

  1. 这些新通道的代价和。
  2. 这些新通道中代价最小的是多少。
  3. 这些新通道中代价最大的是多少。

输入格式

第一行 \(n\) 表示点数。

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

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

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

输出格式

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

样例 #1

样例输入 #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

提示

对于 \(100\%\) 的数据,\(1\le n\le 10^6,1\le q\le 5\times 10^4,\sum k\le 2\times n\)

每个测试点的具体限制见下表:

测试点编号 \(n\) 特殊性质
\(1\sim 2\) \(\le 10^4\)
\(3\sim 5\) \(\le 10^5\) 树的形态是链
\(6\sim 7\) \(\le 10^5\)
\(8\sim 10\) \(\le 10^6\)

解题思路

虚树

虚树:虚树是整棵树的一部分,即所有的查询点和所有查询点的dfs序的相邻节点的lca所形成的点集构成的树,如果没有lca,则这些点可能没有根,故需要lca将这些点拼接起来。关键在于如何遍历这棵虚树,一般有两种遍历方式:

  1. 将这些点按欧拉序(同时记录入栈和出栈的时间戳)排序,用栈存储这些点,模拟dfs的过程
  2. 直接将这棵虚树建立起来,同时一般为了方便,常常引入根节点 \(1\),构建过程比较繁琐,大概就是先将所有询问点按dfs序排序,然后用一个栈维护信息,即栈中次顶节点为栈顶节点的父亲,一开始先将根节点 \(1\) 入栈,然后遍历所有询问点,判断询问点和栈顶节点的lca,如果lca等于栈顶节点,则此时栈顶节点为询问点父亲,直接将询问点压入栈中,否则说明当前询问点不在栈中所存的链上,此时需要将栈中的节点弹出建边使栈中所有节点包括当前查询点在一条链上

本题要求任意两个查询点的代价和、任意两个查询点的最小/大代价
求任意两个查询点的代价和
\(s[i]\) 表示以 \(i\) 为根的子树中所有的查询点到 \(i\) 的代价和,\(sz[i]\) 表示以 \(i\) 为根的子树中含有的查询点的数量。考虑两棵分别以 \(u、v\) 为根的子树的所有查询点之间的代价和,设两棵子树中存在的两个查询点为 \(a,b\),则这两棵子树 \(a、b\) 的代价为:\((s[a]+sz[a]\times dis(a,u))\times sz[v]+(s[b]+sz[b]\times dis(b,v))\times sz[u]\),表示 \(a/b\) 里的每条路径被统计了 \(sz[b]/sz[a]\) 次,将所有这些两两查询点的代价统计起来即为 \(u,v\) 的代价和,现在考虑一般情况:假设 \(u\) 这个根正在遍历 \(v_i\) 这个儿子,则 \(s[u]\) 存储的是 \(s[v_1]\sim s[v_{i-1}]\) 的代价和,\(sz[u]\) 存储的是 \(sz[v_1]\sim sz[v_{i-1}]\) 的代价和,则 \(v_i\) 这棵子树的代价为 \((s[v_i]+sz[v_i]\times dis(v_i,u))+s[u]\times sz[v_i]\)

求任意两个查询点的最小/大代价
以最大值为例,最小值同理。假设 \(u\) 这个根正在遍历 \(v_i\) 这个儿子,\(mx[u]\) 存储的是 \(mx[v_1]\sim mx[v_{i-1}]\) 中的最大值,当前查询点到 \(u\) 的最大值为 \(mx[v_i]+dis(v_i,u)\),则当前最大值为 \(mx[u]+mx[v_i]+dis(v_i,u)\),然后用 \(mx[v_i]+dis(v_i,u)\) 更新 \(mx[u]\) 即可

  • 时间复杂度:\(O(nlogn)\)

代码

  • 模拟dfs
#pragma GCC optimize(3)


// Problem: 大工程
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2921/
// Memory Limit: 512 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e6+5,inf=0x3f3f3f3f;
int n,q,k;
vector<int> adj[N];
int a[N],dep[N],sz[N],mn[N],mx[N],in[N],out[N],f[N][20],cnt,t;
LL s[N];
stack<int> stk;
bool v[N];
void dfs(int x,int father)
{
	in[x]=++cnt;
	f[x][0]=father;
	for(int i=1;i<=t;i++)f[x][i]=f[f[x][i-1]][i-1];
	for(int y:adj[x])
	{
		if(y==father)continue;
		dep[y]=dep[x]+1;
		dfs(y,x);
	}
	out[x]=++cnt;
}
int lca(int x,int y)
{
	if(dep[x]>dep[y])swap(x,y);
	for(int i=t;i>=0;i--)
		if(dep[f[y][i]]>=dep[x])y=f[y][i];
	if(x==y)return x;
	for(int i=t;i>=0;i--)
		if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
bool cmp(int &x,int &y)
{
	return (x>0?in[x]:out[-x])<(y>0?in[y]:out[-y]);
}
int main()
{
    read(n);
    t=__lg(n);
    for(int i=1;i<n;i++)
    {
    	int a,b;
    	read(a),read(b);
    	adj[a].pb(b),adj[b].pb(a);
    	mn[i]=inf;
    }
    mn[n]=inf;
    dep[1]=1;
    dfs(1,0);
    read(q);
    while(q--)
    {
    	read(k);
    	for(int i=1;i<=k;i++)read(a[i]),sz[a[i]]=1,mn[a[i]]=0,v[a[i]]=true;
    	sort(a+1,a+1+k,cmp);
    	cnt=k;
    	a[++cnt]=-a[1];
    	for(int i=2;i<=k;i++)
    	{
    		a[++cnt]=-a[i];
    		int Lca=lca(a[i-1],a[i]);
    		if(!v[Lca])a[++cnt]=Lca,a[++cnt]=-Lca,v[Lca]=true;
    	}
    	sort(a+1,a+1+cnt,cmp);
    	LL res1=0;
    	int res2=inf,res3=0;
    	for(int i=1;i<=cnt;i++)
    	{
    		if(a[i]>0)stk.push(a[i]);
    		else
    		{
    			int x=stk.top();
    			stk.pop();
    			if(stk.size())
    			{
    				int fa=stk.top();
    				int d=dep[x]-dep[fa];
    				res1+=(s[x]+d*sz[x])*sz[fa]+s[fa]*sz[x];
    				sz[fa]+=sz[x];
    				s[fa]+=s[x]+d*sz[x];
    				mn[x]+=d,mx[x]+=d;
    				res2=min(res2,mn[fa]+mn[x]);
    				res3=max(res3,mx[fa]+mx[x]);
    				mn[fa]=min(mn[fa],mn[x]);
    				mx[fa]=max(mx[fa],mx[x]);
    			}
    			mx[x]=sz[x]=s[x]=v[x]=0,mn[x]=inf;
    		}
    	}
    	printf("%lld %d %d\n",res1,res2,res3);
    }
    return 0;
}
  • 构建虚树
#pragma GCC optimize(3)
// Problem: 大工程
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2921/
// Memory Limit: 512 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e6+5,inf=0x3f3f3f3f;
int n,q,k;
vector<int> adj[N];
int a[N],dep[N],sz[N],mn[N],mx[N],dfn[N],f[N][20],cnt,t,res2,res3;
LL s[N],res1;
int stk[N],top;
bool v[N];
void dfs(int x,int father)
{
	dfn[x]=++cnt;
	f[x][0]=father;
	for(int i=1;i<=t;i++)f[x][i]=f[f[x][i-1]][i-1];
	for(int y:adj[x])
	{
		if(y==father)continue;
		dep[y]=dep[x]+1;
		dfs(y,x);
	}
}
int lca(int x,int y)
{
	if(dep[x]>dep[y])swap(x,y);
	for(int i=t;i>=0;i--)
		if(dep[f[y][i]]>=dep[x])y=f[y][i];
	if(x==y)return x;
	for(int i=t;i>=0;i--)
		if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
bool cmp(int &x,int &y)
{
	return dfn[x]<dfn[y];
}
void build()
{
	sort(a+1,a+1+k,cmp);
	stk[top=1]=1;
	adj[1].clear();
	for(int i=1;i<=k;i++)
		if(a[i]!=1)
		{
			int l=lca(a[i],stk[top]);
			if(l!=stk[top])
			{
				while(dfn[l]<dfn[stk[top-1]])
					adj[stk[top-1]].pb(stk[top]),top--;
				if(dfn[l]>dfn[stk[top-1]])
					adj[l].clear(),adj[l].pb(stk[top]),stk[top]=l;
				else
					adj[l].pb(stk[top--]);
			}
			adj[a[i]].clear(),stk[++top]=a[i];
		}
	for(int i=1;i<top;++i) adj[stk[i]].pb(stk[i+1]);
}
void dfs(int x)
{
	sz[x]=v[x],s[x]=0;
	if(v[x]) mx[x]=mn[x]=0,v[x]=false;
	else 
	    mn[x]=inf,mx[x]=-inf;
	for(int y:adj[x])
	{
		dfs(y);
		int d=dep[y]-dep[x];
		res1+=(s[y]+d*sz[y])*sz[x]+s[x]*sz[y];
		sz[x]+=sz[y];
		s[x]+=s[y]+d*sz[y];
		mn[y]+=d,mx[y]+=d;
		res2=min(res2,mn[x]+mn[y]);
		res3=max(res3,mx[x]+mx[y]);
		mn[x]=min(mn[x],mn[y]);
		mx[x]=max(mx[x],mx[y]);
	}
}
int main()
{
    read(n);
    t=__lg(n);
    for(int i=1;i<n;i++)
    {
    	int a,b;
    	read(a),read(b);
    	adj[a].pb(b),adj[b].pb(a);
    	mn[i]=inf;
    }
    mn[n]=inf;
    dep[1]=1;
    dfs(1,0);
    read(q);
    while(q--)
    {
    	read(k);
    	for(int i=1;i<=k;i++)read(a[i]),v[a[i]]=true;
    	build();
    	res1=0;
    	res2=inf,res3=0;
    	dfs(1);
    	printf("%lld %d %d\n",res1,res2,res3);
    }
    return 0;
}
posted @ 2022-07-27 18:36  zyy2001  阅读(16)  评论(0编辑  收藏  举报