这题的dp思想还是比较容易想的,关键是如何保证时间复杂度,这时就用到了虚树的技巧。
1.虚树是什么?(虚树的性质)
不妨设现在询问给出了 k k k个点,我们命名这些节点为关键节点。
那么在我的建边方式中,虚树就是:
- 仅包括这些关键节点、它们两两之间的 l c a lca lca以及原树的根的一棵有向树。
- 但仍然保持原树的形态和祖先关系。即若关键节点 a a a在原树中是关键节点 b b b的祖先,则在虚树中, a a a仍是 b b b的祖先。(这里的祖先包括父亲)
故只要我们按这个方法建出虚树,就会有: 虚树的点数 < = 2 × k \text{虚树的点数}<=2\times k 虚树的点数<=2×k。不太严谨的证明:我们将其按 d f n dfn dfn排序,那么相邻点的 l c a lca lca一定比较远点的 l c a lca lca深,也就是更接近那两个点。那么这样最多也就会新加入一个 l c a lca lca,所以总共最多就 2 × k 2\times k 2×k个点。
2.怎么建虚树?
我们先将原树的 d f n dfn dfn预处理出来,然后我们把所有的关键节点按 d f n dfn dfn序排序。排完序后按顺序一个一个插入(接下来讲的 i n s e r t ( ) insert() insert()函数)。
同时我们也需要维护一个 s t a c k stack stack,初始只有一个点 r o o t root root( 1 1 1号节点), t o p = 1 top=1 top=1。
这个栈用来维护什么呢?不妨设我们现在 i n s e r t ( u ) insert(u) insert(u)。那么我们把 i n s e r t ( u ) insert(u) insert(u)函数执行完后,需要保证这个栈的栈顶是 u u u,然后维护的是原树中从根到栈顶( u u u)路径上需要加入虚树的节点,或者也可以理解为虚树中从根到栈顶(u)的路径上的点,所以若把这个栈中的点依次两两连起边来,便是一条链,但是现在还未连边。而且我们需要保证除了栈中的点之间的连边之外,已经 i n s e r t insert insert的点之间的连边都已连好了。
不妨设我们现在要
i
n
s
e
r
t
(
u
)
insert(u)
insert(u),那么我们先求出
l
c
a
(
u
,
s
t
[
t
o
p
]
)
lca(u,st[top])
lca(u,st[top]),然后我们设原来的栈长什么样:
s
t
a
c
k
=
{
1
,
.
.
.
,
s
t
[
k
−
1
]
,
s
t
[
k
]
,
.
.
.
,
s
t
[
t
o
p
]
}
stack=\{1,...,st[k-1],st[k],...,st[top]\}
stack={1,...,st[k−1],st[k],...,st[top]}。
然后我们找到 l c a lca lca在这条链中的位置,即 d e e p [ s t [ k − 1 ] ] < d e e p [ l c a ] ⩽ d e e p [ s t [ k ] ] deep[st[k-1]]<deep[lca]\leqslant deep[st[k]] deep[st[k−1]]<deep[lca]⩽deep[st[k]],那么 s t [ k ] st[k] st[k]、 s t [ k + 1 ] st[k+1] st[k+1]… s t [ t o p ] st[top] st[top]必然是 l c a lca lca的子/孙(因为 l c a lca lca是 s t [ t o p ] st[top] st[top]和 u u u的祖先)。
那么我们考虑新建出来的栈长什么样: s t a c k = { 1 , . . . , s t [ k − 1 ] , l c a , u } stack=\{1,...,st[k-1],lca,u\} stack={1,...,st[k−1],lca,u}。
所以我们需要 l c a lca lca、 s t [ k ] st[k] st[k]、 s t [ k + 1 ] st[k+1] st[k+1]、…、 s t [ t o p ] st[top] st[top]连起来并弹出栈。
然后再将 l c a lca lca和 u u u入栈。
最后 i n s e r t insert insert完所有点后,我们将栈中剩下的点连起来并清空栈。
为什么要按 d f n dfn dfn排序?因为按 d f n dfn dfn排序后,对于相邻的两个关键节点 a a a、 b b b, l c a ( a , b ) lca(a,b) lca(a,b)一定比 a a a和其它点的 l c a lca lca深度深,这样能保证建的点和边不重不漏。
然后看代码:
void insert(int u)
{
if(top==1)//特判top=1
{
if(st[top]!=u)st[++top]=u;//特判u=1
return;
}
int lca=LCA(u,st[top]);
for(;top>1&&dfn[st[top-1]]>=dfn[lca];top--)//建边,因为stack中存的是一条链,所以dfn[st[top-1]]>=dfn[lca]其实和deep[st[top-1]]>=deep[lca]是等价的
add_edge(st[top-1],st[top]);
//做完上面这段循环后,能保证dfn[st[top-1]]<dfn[lca]<=dfn[st[top]]
if(st[top]!=lca)//判断一下,防止自环
{
add_edge(lca,st[top]);
st[top]=lca;
}
st[++top]=u;
}
不理解?来看图模拟一下:
不妨设现在 k = 5 k=5 k=5,关键节点为 4 4 4、 7 7 7、 9 9 9、 10 10 10、 13 13 13(已标红), s t a c k = { 1 } stack=\{1\} stack={1}。
然后我们要先 i n s e r t ( 4 ) insert(4) insert(4),发现 t o p = 1 top=1 top=1,于是 s t a c k = { 1 , 4 } stack=\{1,4\} stack={1,4}。
然后 i n s e r t ( 7 ) insert(7) insert(7),发现 l c a ( 7 , 4 ) = 1 lca(7,4)=1 lca(7,4)=1,于是建边 ( 1 , 4 ) (1,4) (1,4), s t a c k = { 1 , 7 } stack=\{1,7\} stack={1,7}。
然后 i n s e r t ( 9 ) insert(9) insert(9),发现 l c a ( 7 , 9 ) = 6 lca(7,9)=6 lca(7,9)=6,于是建边 ( 6 , 7 ) (6,7) (6,7), s t a c k = { 1 , 6 , 9 } stack=\{1,6,9\} stack={1,6,9}。
然后 i n s e r t ( 10 ) insert(10) insert(10),发现 l c a ( 9 , 10 ) = 9 lca(9,10)=9 lca(9,10)=9,于是 s t a c k = { 1 , 6 , 9 , 10 } stack=\{1,6,9,10\} stack={1,6,9,10}。
然后 i n s e r t ( 13 ) insert(13) insert(13),发现 l c a ( 10 , 13 ) = 8 lca(10,13)=8 lca(10,13)=8,于是建边 ( 9 , 10 ) (9,10) (9,10)、 ( 8 , 9 ) (8,9) (8,9), s t a c k = { 1 , 6 , 8 , 13 } stack=\{1,6,8,13\} stack={1,6,8,13}。
最后将栈清空:建边 ( 8 , 13 ) (8,13) (8,13)、 ( 6 , 8 ) (6,8) (6,8)、 ( 1 , 6 ) (1,6) (1,6), s t a c k = { } stack=\{\} stack={}。
所以建出来的虚树长这个样:
现在应该理解了吧
然后我们可以做一点小优化:若关键节点 a a a是关键节点 b b b的祖先,那么显然只要让 a a a和根断掉, b b b就绝对会和根断掉,所以我们可以忽略 b b b。
3.怎么dp?
不妨设 m i n n [ u ] minn[u] minn[u]为原树中从根节点到 u u u路径上的边权最小值, d p [ u ] dp[u] dp[u]为把以 u u u根节点的子树中的所有关键节点都与根节点切断的最小代价。
那么要么是在 u u u的子树中就切断,或者自己切断:
d p [ u ] = m i n ( ∑ v = s o n [ u ] d p [ v ] , m i n n [ u ] ) dp[u]=min(\sum_{v=son[u]}dp[v],minn[u]) dp[u]=min(v=son[u]∑dp[v],minn[u])
那么直接树形 d p dp dp就好了。
代码如下:
#include<bits/stdc++.h>
#define LN 18
#define N 250010
#define ll long long
#define INF 0x7fffffffffffffff
using namespace std;
struct Tree
{
int cnt,head[N],nxt[N<<1],to[N<<1],w[N<<1];
void init()
{
cnt=0;
memset(head,0,sizeof(head));
}
void adde(int u,int v,int c=0)
{
to[++cnt]=v;
w[cnt]=c;
nxt[cnt]=head[u];
head[u]=cnt;
}
}e1,e2;
int n,m,idx;
int top,st[N];
int a[N];
int d[N],dfn[N],fa[N][LN];
ll minn[N],dp[N];
void dfs(int u)
{
dfn[u]=++idx;
for(int i=1;i<=17;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=e1.head[u];i;i=e1.nxt[i])
{
int v=e1.to[i];
if(v!=fa[u][0])
{
fa[v][0]=u;
d[v]=d[u]+1;
minn[v]=min(minn[u],(ll)e1.w[i]);
dfs(v);
}
}
}
int LCA(int a,int b)
{
if(d[a]<d[b])swap(a,b);
for(int i=17;i>=0;i--)
if(d[fa[a][i]]>=d[b])
a=fa[a][i];
if(a==b)
return a;
for(int i=17;i>=0;i--)
if(fa[a][i]!=fa[b][i])
a=fa[a][i],b=fa[b][i];
return fa[a][0];
}
bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
void insert(int u)
{
if(top==1)
{
if(u!=1)st[++top]=u;
return;
}
int lca=LCA(u,st[top]);
if(lca==st[top])
return;
for(;top>1&&dfn[st[top-1]]>=dfn[lca];top--)
e2.adde(st[top-1],st[top]);
if(st[top]!=lca)
{
e2.adde(lca,st[top]);
st[top]=lca;
}
st[++top]=u;
}
void dfs2(int u)
{
if(!e2.head[u])
{
dp[u]=minn[u];
return;
}
dp[u]=0ll;
for(int i=e2.head[u];i;i=e2.nxt[i])
{
int v=e2.to[i];
dfs2(v);
dp[u]+=dp[v];
}
dp[u]=min(dp[u],minn[u]);
e2.head[u]=0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e1.adde(u,v,w),e1.adde(v,u,w);
}
d[1]=1,minn[1]=INF;
dfs(1);
scanf("%d",&m);
while(m--)
{
int k;
scanf("%d",&k);
for(int i=1;i<=k;i++)
scanf("%d",&a[i]);
sort(a+1,a+k+1,cmp);
e2.cnt=0;
st[top=1]=1;
for(int i=1;i<=k;i++)
insert(a[i]);
for(;top>1;top--)
e2.adde(st[top-1],st[top]);
dfs2(1);
printf("%lld\n",dp[1]);
}
return 0;
}