洛谷传送门
BZOJ传送门
题目描述
在一场战争中,战场由 n n 个岛屿和个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为 1 1 的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。
侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到 1 1 号岛屿上)。不过侦查部门还发现了这台机器只能够使用次,所以我们只需要把每次任务完成即可。
输入输出格式
输入格式:
第一行一个整数 n n ,代表岛屿数量。
接下来行,每行三个整数 u,v,w u , v , w ,代表 u u 号岛屿和号岛屿由一条代价为 c c 的桥梁直接相连,保证且 1≤c≤100000 1 ≤ c ≤ 100000 。
第 n+1 n + 1 行,一个整数 m m ,代表敌方机器能使用的次数。
接下来行,每行一个整数 ki k i ,代表第 i i 次后,有个岛屿资源丰富,接下来 k k 个整数,表示资源丰富岛屿的编号。
输出格式:
输出有 m m 行,分别代表每次任务的最小代价。
输入输出样例
输入样例#1:
10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
3
2 10 6
4 5 7 8 3
3 9 4 6
输出样例#1:
12
32
22
说明
【数据规模和约定】
对于的数据, 2≤n≤10,1≤m≤5,1≤ki≤n−1 2 ≤ n ≤ 10 , 1 ≤ m ≤ 5 , 1 ≤ k i ≤ n − 1
对于20%的数据, 2≤n≤100,1≤m≤100,1≤ki≤min(10,n−1) 2 ≤ n ≤ 100 , 1 ≤ m ≤ 100 , 1 ≤ k i ≤ m i n ( 10 , n − 1 )
对于40%的数据, 2≤n≤1000,m>=1,∑ki≤500000,1≤ki≤min(15,n−1) 2 ≤ n ≤ 1000 , m >= 1 , ∑ k i ≤ 500000 , 1 ≤ k i ≤ m i n ( 15 , n − 1 )
对于100%的数据, 2≤n≤250000,m>=1,∑ki≤500000,1≤ki≤n−1 2 ≤ n ≤ 250000 , m >= 1 , ∑ k i ≤ 500000 , 1 ≤ k i ≤ n − 1
解题分析
一道虚树板题, 也是蒟蒻A的第一道虚树。
考虑只有一组询问,
k<n
k
<
n
,我们可以树形dp。对于一个不需要断掉的节点
A
A
, 如果其子树中有需要切断的点的话,我们要么直接切断它与号节点简单路径中花费最小的边, 要么分开断掉每个子树。我们设
sum[i]
s
u
m
[
i
]
为断掉
i
i
号点及其子树的最小花费,是
i
i
号点到号点简单路径上花费最小的边。 对于一个需要断掉的点j, 其
sum
s
u
m
值初始应为
mn[j]
m
n
[
j
]
则有如下方程:
然后就可以一遍 O(N) O ( N ) dp得到答案。
但是如果有 m m 组询问,这样做复杂度是的,只有40分。
然后我们就可以发现如果点数较少的话, 有效点之间的绝大多数点是没有用的,可以压进一条边。换句话说,我们只需要所有有效点和它们两两之间的 LCA L C A 就可以得到所需信息。
那么我们怎么建立这棵树?欧拉序就是我们DFS一棵树的过程,可以完全还原一棵树, 所以我们对树先做一遍DFS,求出每个点的欧拉序, 每次按欧拉序对有效点排序,模拟DFS过程压栈退栈。同时我们可以发现两点之间 LCA L C A 的总个数不会超过 ki×2 k i × 2 ,这样我们的总复杂度是小于等于 O(∑kilog(∑ki)) O ( ∑ k i l o g ( ∑ k i ) ) 的(因为小询问合在一起的总复杂度是小于一个大点)。
细节见代码。
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <limits.h>
#include <cstring>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 500050
#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, cot, cnt, top, ecnt;
int eout[MX], head[MX], ein[MX], topf[MX], fat[MX],
sta[MX], lg[MX], dep[MX], tree[MX], siz[MX], son[MX];
ll mn[MX], sum[MX];
bool inq[MX];
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;
}
struct Edge
{
int to, nex;
ll len;
}edge[MX];
IN void addedge(const int &from, const int &to, const int &len)
{
edge[++cnt] = {to, head[from], len};
head[from] = cnt;
}
namespace LCA//树剖LCA, 其实也可用倍增、RMQ等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;
mn[edge[i].to] = std::min(mn[now], edge[i].len);
DFS1(edge[i].to, now);
siz[now] += siz[edge[i].to];
if(siz[now] > 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, c;
in(dot); mn[1] = INT_MAX;//注意初始化
for (R int i = 1; i < dot; ++i)
in(a), in(b), in(c), addedge(a, b, c), addedge(b, a, c);
in(q); LCA::DFS1(1, 0); LCA::DFS2(1, 1);
W (q--)
{
in(a); b = a;
for (R int i = 1; i <= a; ++i)
in(tree[i]), sum[tree[i]] = mn[tree[i]], inq[tree[i]] = true;
std::sort(tree + 1, tree + 1 + a, cmp);
for (R int i = 1; i < a; ++i)
{
c = LCA::query(tree[i], tree[i + 1]);//加入LCA
if(!inq[c]) tree[++a] = c, inq[c] = true;
}
if(!inq[1]) tree[++a] = 1;//强制加入1号点
b = a;
for (R int i = 1; i <= b; ++i) tree[++a] = -tree[i];
std::sort(tree + 1, tree + 1 + a, cmp);
for (R int i = 1; i <= a; ++i)
{
if(tree[i] > 0) sta[++top] = tree[i];//入栈
else
{
b = sta[top--]; c = sta[top];//栈中上一个是它的父节点,向上合并
if(b != 1) sum[c] += std::min(mn[b], sum[b]);
else printf("%lld\n", sum[1]);
sum[b] = 0, inq[b] = false;//注意清零
}
}
}
}