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;
}
posted @ 2026-06-13 19:52  kzssCCC  阅读(11)  评论(0)    收藏  举报