【虚树DP】 CF613D Kingdom and its Cities
2021-07-07 10:11:00 # ACM

题链

树形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$部分采用树剖;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#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;
}

Prev
2021-07-07 10:11:00 # ACM
Next