【虚树dp】 CF613D Kingdom and its Cities
树形dp
令f[u]表示以u为根的子树需要的最小点数;
令g[u]表示以u为根的子树未被截断的点数;
对于一个点u,其孩子节点v;
f[u] = \(\sum_{}\)f[v];
g[u] = \(\sum_{}\)g[v];
若u是关键点:
则需要截断子树中未被截断的点:
f[u] += g[u];
g[u] = 1;
若u不是关键点:
若子树中未被截断的点>1,则点u需要放置:
f[u] += 1;
g[u] = 0;
若子树中未被截断的点<=1,不需要改动;
优化
由于所给数据的范围对k是有限制的,所以对给出的关键点构造虚树,然后再dp;
代码lca部分采用树剖;
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ls rt<<1
#define rs rt<<1|1
#define MAXN 1e9
#define MS 100009
#define mod 998244353
int n,m,k;
// 建树和树剖所用数组-----------------
vector<int > vc[MS];
int dep[MS],fa[MS],zson[MS],sz[MS];
int top[MS],val[MS],dfn[MS],tot;
// -----------------------------------
int p[MS],mark[MS];
int sta[MS],stp;
int tmp[MS],cnt;
vector<int > vx[MS];
int ff[MS],gg[MS];
void dfs1(int u,int f){
sz[u] = 1;
dep[u] = dep[f]+1;
fa[u] = f;
zson[u] = 0;
int maxnzson = 0;
for(auto &v:vc[u]){
if(v != f){
dfs1(v,u);
sz[u] += sz[v];
if(sz[v] > maxnzson){
maxnzson = sz[v];
zson[u] = v;
}
}
}
}
void dfs2(int u,int tp){
top[u] = tp;
dfn[u] = ++tot;
val[tot] = 0;
if(zson[u]) dfs2(zson[u],tp);
for(auto &v:vc[u]){
if(v != fa[u] && v != zson[u]){
dfs2(v,v);
}
}
}
int __lca(int u,int v){
while(top[u] != top[v]){
if(dep[ top[u] ] < dep[ top[v] ]) swap(u,v);
u = fa[top[u]];
}
if(dep[u] < dep[v]) return u;
return v;
}
bool cmp(int t1,int t2){
return dfn[t1] < dfn[t2];
}
void insert(int x){ // 插入点x
if(x == 1) return; // 根节点
if(stp == 1){ // 栈中元素只有一个
sta[++stp] = x;
return;
}
int lca = __lca(x,sta[stp]);
if(lca == sta[stp]){ // 栈顶元素是x的父亲
sta[++stp] = x;
return;
}
// 依次弹出栈中元素,使得dfn[栈顶] <= dfn[x] <= dfn[栈次顶]
while(stp>1 && dfn[lca] <= dfn[sta[stp-1]]){
int u = sta[stp-1];
int v = sta[stp];
tmp[++cnt] = v;
vx[u].push_back(v);
vx[v].push_back(u);
stp--;
}
// 判断栈顶是不是x的父亲
if(lca != sta[stp]){
int u = lca;
int v = sta[stp];
tmp[++cnt] = v;
vx[u].push_back(v);
vx[v].push_back(u);
sta[stp] = lca;
}
sta[++stp] = x;
}
void build(){
stp = 1;
sta[stp] = 1; // 构建虚树所用栈,此栈存储的数永远是树上同一条链
cnt = 1;
tmp[cnt] = 1; // tmp数组存储虚树所用节点,方便清理
for(int i=1;i<=k;i++){
insert(p[i]);
}
while(stp>1){
int u = sta[stp-1];
int v = sta[stp];
tmp[++cnt] = v;
vx[u].push_back(v);
vx[v].push_back(u);
stp--;
}
}
void cal(int u,int f){
for(auto &v:vx[u]){
if(v == f) continue;
cal(v,u);
ff[u] += ff[v];
gg[u] += gg[v];
}
if(mark[u]){
ff[u] += gg[u];
gg[u] = 1;
}
else{
if(gg[u] > 1){
ff[u] += 1;
gg[u] = 0;
}
else{
;
}
}
}
void clean(){
for(int i=1;i<=cnt;i++){
vx[tmp[i]].clear();
ff[tmp[i]] = gg[tmp[i]] = 0;
}
for(int i=1;i<=k;i++){
mark[p[i]] = 0;
}
}
int main() {
ios::sync_with_stdio(false);
cin >> n;
for(int i=1;i<=n-1;i++){
int u,v;
cin >> u >> v;
vc[u].push_back(v);
vc[v].push_back(u);
}
// 树剖
dfs1(1,1);
dfs2(1,1);
cin >> m;
while(m--){
cin >> k;
for(int i=1;i<=k;i++){
cin >> p[i];
mark[p[i]] = 1; // 标记关键点
}
// -1的情况特判
int flag = 1;
for(int i=1;i<=k;i++){
if(mark[ fa[p[i]] ] && fa[p[i]] != p[i]){
flag = 0;
break;
}
}
if(!flag){
cout << -1 << "\n";
}
else{
// 所给关键点按dfs序排序
sort(p+1,p+k+1,cmp);
// 构建虚树
build();
// 树形dp
cal(1,1);
cout << ff[1] << "\n";
}
// 清空所用数组
clean();
}
return 0;
}