【ybt金牌导航5-3-3】【luogu P4103】大工程

41 篇文章 0 订阅
3 篇文章 0 订阅

大工程

题目链接:ybt金牌导航5-3-3 / luogu P4103

题目大意

给你一个树,多次询问,每次给出一些点,要你对于每两个点之间的树上路径,求它们的长度和,最长的长度和最短的长度。
所有询问给出点的总数不会超过树点数的两倍。

思路

首先想一次询问。

容易想到类似点分治的思想,那对于每个点枚举子树搞。
(由于时限开的很够就可以不找中心,直接暴力搞)

至于怎么暴力搞,就先 dfs 预处理出某个点的子树的一些值:
这个子树从它出发的路径长度和,从它出发的最长路径和最短路径。(当然终点一定要是给定点啦)

然后再 dfs 一次,这次就枚举点分治,你可以把存在于两个儿子子树之间的路径和从它出发到它子树的路径搞了,然后再同一个儿子子树中的就递归处理。

然后它是多次询问,但看到总共的点数还是 n n n 的规模,我们就直接上虚树。
然后就好了。

(记得开 long long)

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

struct node {
	int x, to, nxt;
}e[2000001], e_[1000001];
int n, x, y, le[1000001], KK, k[1000001], tmp;
int q, fa[1000001][21], dfn[1000001], num[1000001];
int sta[1000001], deg[1000001], fath[1000001];
int le_[1000001], KK_, maxn[1000001], minn[1000001];
int ansmin, ansmax;
ll sum[1000001], anssum;
bool real[1000001], no1;

void add(int x, int y) {
	e[++KK] = (node){0, y, le[x]}; le[x] = KK;
	e[++KK] = (node){0, x, le[y]}; le[y] = KK;
}

void add_(int x, int y, int z) {
	e_[++KK_] = (node){z, y, le_[x]}; le_[x] = KK_;
}

void dfs(int now, int father) {
	fa[now][0] = father;
	dfn[now] = ++tmp;
	deg[now] = deg[father] + 1;
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) dfs(e[i].to, now);
}

bool cmp(int x, int y) {
	return dfn[x] < dfn[y];
}

int lCA(int x, int y) {
	if (deg[y] > deg[x]) swap(x, y);
	for (int i = 20; i >= 0; i--)
		if (deg[fa[x][i]] >= deg[y])
			x = fa[x][i];
	if (x == y) return x;
	for (int i = 20; i >= 0; i--)
		if (fa[x][i] != fa[y][i]) {
			x = fa[x][i];
			y = fa[y][i];
		}
	return fa[x][0];
}

void build() {//建虚树
	sort(k + 1, k + k[0] + 1, cmp);
	
	int nn = k[0];
	for (int i = 1; i <= nn; i++) {
		if (!sta[0]) {
			sta[++sta[0]] = k[i];
			fath[k[i]] = 1;
			continue;
		}
		int lca = lCA(k[i], sta[sta[0]]);
		while (deg[sta[sta[0]]] > deg[lca]) {
			if (deg[sta[sta[0] - 1]] < deg[lca])
				fath[sta[sta[0]]] = lca;
			sta[0]--;
		}
		if (lca != sta[sta[0]]) {
			k[++k[0]] = lca;
			fath[lca] = sta[sta[0]];
			sta[++sta[0]] = lca;
		}
		fath[k[i]] = lca;
		sta[++sta[0]] = k[i];
	}
	
	for (int i = 1; i <= k[0]; i++)//建虚树
		if (k[i] != 1)
			add_(fath[k[i]], k[i], deg[k[i]] - deg[fath[k[i]]]);//边长就是深度差
	
	sta[0] = 0;//把栈清空
}

void dfs1(int now) {//第一次 dfs 预处理值
	num[now] = sum[now] = 0;
	maxn[now] = -INF; minn[now] = INF;
	if (real[now]) {
		num[now] = 1;
		minn[now] = 0;
		maxn[now] = 0;
	}
	for (int i = le_[now]; i; i = e_[i].nxt) {
		dfs1(e_[i].to);
		num[now] += num[e_[i].to];
		sum[now] += 1ll * e_[i].x * num[e_[i].to] + sum[e_[i].to];
		maxn[now] = max(maxn[now], maxn[e_[i].to] + e_[i].x);
		minn[now] = min(minn[now], minn[e_[i].to] + e_[i].x);
	}
}

void dfs2(int now) {//第二次 dfs DP(分治思想)
	int nowmax = -INF, nowmin = INF, nownum = 0;
	ll nowsum = 0;
	if (real[now]) {
		nownum = 1;
		nowmin = 0;
		nowmax = 0;
	}
	for (int i = le_[now]; i; i = e_[i].nxt) {
		ansmax = max(ansmax, nowmax + maxn[e_[i].to] + e_[i].x);
		ansmin = min(ansmin, nowmin + minn[e_[i].to] + e_[i].x);
		anssum += 1ll * sum[e_[i].to] * nownum + 1ll * nowsum * num[e_[i].to] + 1ll * nownum * num[e_[i].to] * e_[i].x;
		
		nowmax = max(nowmax, maxn[e_[i].to] + e_[i].x);
		nowmin = min(nowmin, minn[e_[i].to] + e_[i].x);
		nownum += num[e_[i].to];
		nowsum += 1ll * e_[i].x * num[e_[i].to] + sum[e_[i].to];
		
		dfs2(e_[i].to);
	}
	
	real[now] = 0;
	le_[now] = 0;
} 

int main() {
//	freopen("read.txt", "r", stdin);
	
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	}
	
	dfs(1, 0);//建虚树的预处理
	for (int i = 1; i <= 20; i++)
		for (int j = 1; j <= n; j++)
			fa[j][i] = fa[fa[j][i - 1]][i - 1];
	
	scanf("%d", &q);
	while (q--) {
		scanf("%d", &k[0]);
		no1 = 1;
		for (int i = 1; i <= k[0]; i++) {
			scanf("%d", &k[i]);
			real[k[i]] = 1;
			if (k[i] == 1) no1 = 0;
		}
		
		if (no1) k[++k[0]] = 1;
		build();
		
		dfs1(1);
		
		ansmin = INF;
		ansmax = 0;
		anssum = 0;
		dfs2(1);
		
		printf("%lld %d %d\n", anssum, ansmin, ansmax);
		
		KK_ = 0;
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值