CF2207D 思路分享(博弈论,树上 dp)
https://codeforces.com/problemset/problem/2207/D
题意概述
给定一棵 \(n\) 个节点的树和 \(A\) 和起点 \(s\),\(A\) 走到某个叶子即获胜,每次可以选择沿一条边走到下一个节点或者不动;\(B\) 可以选择在自己回合占用一条边,\(A\) 不可以走被占用的边,\(B\) 的这个能力有 \(k\) 回合的冷却.
\(A\) 先手,问 \(A\) 是否能获胜.
\(3\le n \le 5\cdot 10^5\).
思路
对于一个节点 \(i\),如果存在两个必胜点使得 \(i\) 到这两个必胜点的距离之和 \(\le k+1\),则 \(i\) 为必胜点.
策略为:\(A\) 向任意一个必胜点走,\(B\) 在最后一步堵上最后一条边,然后 \(A\) 走向另一个必胜点.
以 \(s\) 为根,初始所有叶子为必胜点,\(dfs\) 的过程中,需要维护孩子中最近和次近的两个必胜点,两必胜点不能来自同一棵子树.
令 \(dp[i]\) 为 \(i\) 的子树中最小的必胜点深度.
对节点 \(u\),寻找其孩子最小和次小的 \(dp\) 值,判断是否满足条件,如果满足条件,\(dp[u]=depth[u]\),并标记 \(u\) 为必胜点;否则 \(dp[u]\) 继承其孩子的最小 \(dp\) 值.
最后判断 \(s\) 是否为必胜点即可.
时间复杂度 \(\mathcal{O}(n)\).
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9;
void solve(){
int n,k,s;
cin >> n >> k >> s;
vector<vector<int>> adj(n+1);
for (int i=0;i<n-1;i++){
int u,v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
vector<int> dp(n+1,INF),depth(n+1);
vector<bool> f(n+1,false);
function<void(int,int)> dfs = [&](int u,int par){
if (adj[u].size()==1){
dp[u] = depth[u];
f[u] = true;
return;
}
int minn1=INF,minn2=INF;
for (auto& v:adj[u]){
if (v==par) continue;
depth[v] = depth[u]+1;
dfs(v,u);
minn2 = min(minn2,dp[v]);
if (minn2<minn1){
swap(minn1,minn2);
}
}
if (minn1!=INF && minn2!=INF && minn1+minn2-2*depth[u]<=k+1){
f[u] = true;
dp[u] = depth[u];
}
else{
dp[u] = minn1;
}
};
dfs(s,-1);
if (f[s]){
cout << "YES" << '\n';
}
else{
cout << "NO" << '\n';
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号