CF2257C Spying on the Beaver
树上深搜肯定是要用的,常见套路是以 \(u\) 为子树考虑问题。
很容易想到以 \(u\) 是不是标记点来分类。手玩后发现,假如有 \(cnt\) 个 \(v\) 是标记点的话:
第一,如果 \(u\) 是标记点,则要对答案的贡献是 \(cnt\)。
第二,如果 \(u\) 不是标记点,则对答案的贡献是 \(cnt - 1\)。
本来以为到这里就做完了,结果发现处理不了多个子树内只有一个标记点的情况。然后就被卡住了,看了别人的题解,发现只需要把 \(g[i]\) 的定义从 \(i\) 是不是标记点改为以 \(i\) 为根的子树内有无标记点,然后自下而上的时候更新一下就能解决这个问题了。
可以理解为:单标记点早检测晚检测都是一样的,所以在父亲处选边检测也是对的。因此向上传递做集中处理。而如果到最后它都是一个单标记点的话,那就不需要检测了。
时间复杂度 \(O(TN)\)。
可以总结成一个贡献延后计算的小技巧积累~
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l); i <= (r); ++ i)
#define G(i,r,l) for(int i(r); i >= (l); -- i)
using namespace std;
using ll = long long;
const int N = 2e5;
struct node{
int v, ne;
}e[N << 1];
int first[N];
int n, m, T, ans, idx;
int g[N], an[N];
void add(int u, int v){
e[++ idx] = (node){v, first[u]};
first[u] = idx;
}
void dfs(int u, int fa){
int st = 0;
for(int i = first[u]; i; i = e[i].ne){
int v = e[i].v;
if(v == fa) continue;
dfs(v, u);
if(g[v] == 1){
st = 1;
++ ans;
an[ans] = v;
}
}
if(st == 1){
if(g[u] == 0) -- ans;
g[u] = 1;
}
}
void Main(){
ans = idx = 0;
cin >> n;
F(i, 1, n){
first[i] = g[i] = 0;
}
F(i, 2, n){
int pa;
cin >> pa;
add(i, pa);
add(pa, i);
}
cin >> m;
F(i, 1, m){
int u;
cin >> u;
g[u] = 1;
}
dfs(1, 0);
cout << ans << ' ';
F(i, 1, ans) cout << an[i] << ' ';
cout << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> T;
while(T --) Main();
return fflush(0), 0;
}

浙公网安备 33010602011771号