大工程
题目链接: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;
}