洛谷传送门
BZOJ传送门
题目描述
国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。
我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。
在 2 2 个国家 之间建一条新通道需要的代价为树上 a,b a , b 的最短路径。
现在国家有很多个计划,每个计划都是这样,我们选中了 k k 个点,然后在它们两两之间 新建 条 新通道。现在对于每个计划,我们想知道: 1 1 .这些新通道的代价和 .这些新通道中代价最小的是多少 3 3 .这些新通道中代价最大的是多少
输入输出格式
输入格式:
第一行 表示点数。
接下来 n−1 n − 1 行,每行两个数 a,b a , b 表示 a a 和 之间有一条边。点从 1 1 开始标号。
接下来一行 表示计划数。对每个计划有 2 2 行,第一行 表示这个计划选中了几个点。
第二行用空格隔开的 k k 个互不相同的数表示选了哪 个点。
输出格式:
输出 q q 行,每行三个数分别表示代价和,最小代价,最大代价。
输入输出样例
输入样例#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
说明
对于第 1,2 个点:
对于第 3,4,5 个点: n≤100000 n ≤ 100000 ,交通网络构成一条链
对于第 6,7 个点: n≤100000 n ≤ 100000
对于第 8,9,10 个点: n≤1000000 n ≤ 1000000
对于所有数据, q≤50000 q ≤ 50000 并且保证所有k之和 ≤2∗n ≤ 2 ∗ n
解题分析
看到 ∑ki≤2∗n ∑ k i ≤ 2 ∗ n 就知道应该是一道虚树题了…
对于每个询问, 我们用欧拉序建出虚树, 再模拟DFS过程。
对于求最大最小值, 我们DFS的时候不断在父节点处更新子树内两点间距离最大最小值即可。
稍微难搞一点的是代价和。 我们假设树被分为两部分 S1 S 1 、 S2 S 2 ,那么显然连接这两部分的那条边 A A 被计算的次数是,既可以在DFS时一边爬树一边处理也可以处理出子树大小计算。
总复杂度还是 O(Nlog(N)) O ( N l o g ( N ) )
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <limits.h>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 1005000
#define ll long long
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
W (!isdigit(c)) c = gc;
W (isdigit(c))
x = (x << 1) + (x << 3) + c - 48, c = gc;
}
int dot, q, cnt, cot, top;
int fat[MX], son[MX], dep[MX], topf[MX], ein[MX],
eout[MX], sta[MX], tree[MX << 1], head[MX];
ll mn[MX], sum[MX], siz[MX], mx[MX], ansmx, ansmn, anstot;
bool inq[MX];
struct Edge
{
int to, nex;
}edge[MX << 1];
IN void addedge(const int &from, const int &to)
{
edge[++cnt] = {to, head[from]};
head[from] = cnt;
}
IN bool cmp(const int &x, const int &y)
{
int key1 = x > 0 ? ein[x] : eout[-x];
int key2 = y > 0 ? ein[y] : eout[-y];
return key1 < key2;
}
namespace LCA
{
void DFS1(const int &now, const int &fa)
{
fat[now] = fa; ein[now] = ++cot; siz[now] = 1;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(edge[i].to == fa) continue;
dep[edge[i].to] = dep[now] + 1;
DFS1(edge[i].to, now);
siz[now] += siz[edge[i].to];
if(siz[edge[i].to] > siz[son[now]]) son[now] = edge[i].to;
}
eout[now] = ++cot;
}
void DFS2(const int &now, const int &grand)
{
topf[now] = grand;
if(!son[now]) return;
DFS2(son[now], grand);
for (R int i = head[now]; i; i = edge[i].nex)
{
if(edge[i].to == fat[now] || edge[i].to == son[now]) continue;
DFS2(edge[i].to, edge[i].to);
}
}
IN int query(R int x, R int y)
{
W (topf[x] != topf[y])
{
if(dep[topf[x]] < dep[topf[y]]) std::swap(x, y);
x = fat[topf[x]];
}
return dep[x] < dep[y] ? x : y;
}
}
int main(void)
{
int a, b, num, bd, lca, now, fa, dis;
in(dot);
for (R int i = 1; i < dot; ++i)
in(a), in(b), addedge(a, b), addedge(b, a);
in(q); LCA::DFS1(1, 0); LCA::DFS2(1, 1);
for (R int i = 1; i <= dot; ++i) mn[i] = INT_MAX;//如果不是关键点而是关键点的LCA的情况
//距离最小值应该取子树中的最小值而不是0
W (q--)
{
in(num); anstot = 0, ansmx = 0, ansmn = INT_MAX;
for (R int i = 1; i <= num; ++i)
in(tree[i]), mn[tree[i]] = 0, mx[tree[i]] = 0, siz[tree[i]] = 1, inq[tree[i]] = true;
std::sort(tree + 1, tree + 1 + num, cmp); bd = num;
for (R int i = 1; i < bd; ++i)
{
lca = LCA::query(tree[i], tree[i + 1]);
if(!inq[lca]) tree[++num] = lca, inq[lca] = true, siz[lca] = 0;
}
bd = num;
for (R int i = 1; i <= bd; ++i) tree[++num] = -tree[i];
std::sort(tree + 1, tree + 1 + num, cmp);
for (R int i = 1; i <= num; ++i)
{
if(tree[i] > 0) sta[++top] = tree[i];
else
{
now = sta[top--]; fa = sta[top]; dis = dep[now] - dep[fa];
if(top)//弹完的时候不必计算了
{
sum[now] += dis * siz[now];//更新距离和,将新的一条边加入计算
anstot += sum[now] * siz[fa] + sum[fa] * siz[now];//更新总数
siz[fa] += siz[now], sum[fa] += sum[now];//更新父节点
mn[now] += dis; ansmn = std::min(ansmn, mn[now] + mn[fa]);
mx[now] += dis; ansmx = std::max(ansmx, mx[now] + mx[fa]);
mn[fa] = std::min(mn[fa], mn[now]); mx[fa] = std::max(mx[fa], mx[now]);
}
mx[now] = 0, mn[now] = INT_MAX, siz[now] = 0, sum[now] = 0, inq[now] = false;
//注意清零
}
}
printf("%lld %lld %lld\n", anstot, ansmn, ansmx);
}
}