[CF1534H] Lost Nodes题解

\(\text{CF3500}\),你从没见过的船新 \(\text{DP}\)

前言

感谢同机房不屑发题解的大佬对我的教导。

在获得允许后加入自己理解写下此篇题解 以免大家被平衡树劝退

更好的阅读体验

题意

给你一棵 \(n\) 个点的树。你需要猜出树上的两个特殊节点 \(a,b\)\(a\) 不一定与 \(b\) 不同)。首先,你会被告知一个整数 \(f\),满足节点 \(f\) 是节点 \(a,b\) 之间的简单路径上的一点。接下来你可以进行询问,每次给定整数 \(r\),交互器会给你当树的根节点为 \(r\) 时,节点 \(a,b\) 的最近公共祖先是哪个节点。 在给定树的形态之后,你需要先给出在所有 \(a,b,f\) 的可能的取值中,至少需要多少次询问才能保证得出特殊节点的编号。

一些性质

  1. 交互库返回的是:\(path(a,b)\) 上离 \(r\) 最近的点。
  2. \(f\) 为根作为有根树来处理不影响问题的解决,\(a\)\(b\) 除了在 \(f\) 算答案的时候独立,可以先单独处理 \(a\)

\(a,b\) 若不同则在 \(f\) 两子树内,互不干扰

  1. 我们询问一定是询问叶节点,因为询问点 \(v\) 不优于 询问 \(v\) 子树内的任意一叶节点

\(u=fa_v\) ,首先 \(a\)\(u\) 子树内

\(a\) 不在 \(v\) 子树内,询问 \(v\) 和叶节点效果相同,返回都是 \(u\)

\(a\)\(v\) 子树内,询问 \(v\) 只能知道在 \(v\) 子树内,而询问叶节点不仅可以知道是在 \(v\) 子树内,还可能可以进一步确认 \(a\) 的位置。

Simple DP

我们发现我们的询问策略很诡异,直接询问叶节点,所以对于当前考虑的子树(除了根所代表的整棵树),一定在祖先的时候就询问过一个叶节点(恰好一个),也就排除了一个子树,这时我们只用考虑其他的子树。即我们可以不费代价地得到一个子树的信息,但代价必须提前算在叶节点上。

\(f_u\) 表示考虑 \(u\) 所代表的子树, \(a\) 在子树内,加上祖先向该子树叶节点的一次询问,需要确定 \(a\) 的位置的最小代价。

对于叶节点 \(u\)\(f_u=1\)

对于非叶节点 \(u\) ,若我们以 \(v_{0,1,2...}\) 的顺序访问其儿子

  1. \(a\) 不在 \(v_i\) 子树内,一次询问就可以知道
  2. 若在 \(v_i\) 的子树内,因为我们有一次从祖先来的询问机会(让其询问 \(v_0\) 的子树的叶节点),所以只需要额外询问 \(i\) 次,而对于 \(v_i\) 子树内叶节点的询问次数又算到了 \(f_{v_i}\)

所以转移方程\(f_u=\max_{i=0} \{f_{v_i}+i\}\)

显然,我们把 \(v_i\)\(f_{v_i}\) 从大到小排是最优的,同时我们可以维护 \(u\) 节点第一次询问:\(go_u=go_{v_0}\)

对于根节点统计答案\(ans=\displaystyle\max_{0\le i<j < deg_f} f_{v_i}+f_{v_j}+j-1\)

即假设 \(a,b\) 分别 \(v_i,v_j\) 的子树内,对于 \(v_i\) 子树叶节点的询问已经算在了 \(f_{v_i}\) 内,所以从 \(j\) 中减掉。

此时我们可以 \(O(n^2)\) 的求出第一问,第二问用询问 \(go_u\) 不断地找到。

换根优化

发现转移跟儿子的排名(按 \(f\) 降序)是有关的,难道我们要平衡树了吗?那写个锤子

换根 \(DP\) 常用的方法是维护前后缀,然后换到儿子的时候就合并信息,对于此题显然也可以。

要注意维护后缀的时候 \(rank\)\(-1\) ,然后就很 \(easy\)

Code

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define ri register int
using namespace std;

const int maxn = 1e5 + 10;
vector<int> ans,vec[maxn],to[maxn],pre[maxn],saf[maxn];
int f[maxn],go[maxn],pa[maxn];
inline bool cmp(const int &x,const int &y){return f[x] > f[y];}
void dfs(int u,int fa){
	vec[u].resize(0); pa[u] = fa;
	for(ri v : to[u]) if(v != fa) dfs(v,u),vec[u].pb(v);
	if(!vec[u].size()){go[u] = u,f[u] = 1;return;}
	sort(vec[u].begin(),vec[u].end(),cmp);
	go[u] = go[vec[u][0]],f[u] = 1;
	int cnt = -1;
	for(ri v : vec[u]) ++cnt,f[u] = max(f[u],f[v] + cnt);
}
inline int getans(int x){
	int ans = 0;
	for(ri i = 1;i < (int)vec[x].size();++i) ans = max(ans,f[vec[x][i]] + i - 1);
	ans += f[vec[x][0]];return ans;
}
void rdfs(int u,int fa){
	vec[u] = to[u],sort(vec[u].begin(),vec[u].end(),cmp),ans[u-1] = getans(u);
	int m = vec[u].size(); pre[u].resize(m),saf[u].resize(m);
	for(ri i = 0;i < m;++i) pre[u][i] = f[vec[u][i]] + i;
	for(ri i = 1;i < m;++i) pre[u][i] = max(pre[u][i-1],pre[u][i]);
	for(ri i = m - 1;i >= 0;--i) saf[u][i] = f[vec[u][i]] + i - 1;
	for(ri i = m - 2;i >= 0;--i) saf[u][i] = max(saf[u][i+1],saf[u][i]);
	for(ri i = 0,v;i < m;++i)
		if((v = vec[u][i]) != fa){
			f[u] = 1;
			if(i) f[u] = max(f[u],pre[u][i-1]); if(i < m-1) f[u] = max(f[u],saf[u][i+1]);
			rdfs(v,u);
		}
}
inline int query(int x){
	cout<<"? "<<x<<endl;
	int res; cin>>res; return res;
}
inline int get(int x){
	if(go[x] == x) return x;
	for(ri i = 1;i < (int)vec[x].size();++i){
		int v = go[vec[x][i]],res = query(v);
		if(res != x) return get(res);
	}
	return x;
}
inline void solve(){
	int a = 0,b = 0,x; cin>>x;
	dfs(x,x);
	for(ri v : vec[x]){
		int res = query(go[v]);
		if(res == x) continue;
		if(!a) a = get(res);
		else b = get(res);
		if(b) break;
	}
	if(!a) a = x; if(!b) b = x;
	cout<<"! "<<a<<' '<<b<<endl;
}
inline void init(){
	int n; cin>>n;
	for(ri i = 1,u,v;i < n;++i) cin>>u>>v,to[u].pb(v),to[v].pb(u);
	if(n == 1){puts("0");puts("! 1 1");return;}
	ans.resize(n); dfs(1,1); rdfs(1,1);
	int lans = 0; for(ri it : ans) lans = max(lans,it);
	cout<<lans<<endl;
	solve();
}
int main(){
	init();
}
posted @ 2022-03-16 22:58  Lumos壹玖贰壹  阅读(126)  评论(0编辑  收藏  举报