*题解:CF1491E Fib-tree

题目链接

解析

首先不难发现一次合法的切割操作必定将顶点数为 \(F_n\) 原树分隔成顶点数为 \(F_{n - 1}\) 和 \(F_{n - 2}\) 的两部分。但是这样的切割方式有多种,遍历每种切割方式并往下递归的复杂度是不可接受的。但事实上,我们可以证明,若存在一种合法切割方式使得切割后两连通块仍是 Fib-tree,则所有切割后顶点数合法的方案都合法。

具体地,大小 \(\le F_3\) 的 Fib-tree 显然满足这个条件。假设对于大小 \(\le F_n\) 的 Fib-tree 也满足条件。那么对于大小为 \(F_{n + 1}\) 的 Fib-tree,其必定存在一种合法切割方式使得切割边 \(e_1\) 后变为一个大小为 \(F_{n}\) 的 Fib-tree 和一个大小为 \(F_{n - 1}\) 的 Fib-tree。若这种切割方式切割出来的是大小为 \(F_{n - 1}\) 的子树,其为 Fib-tree,则其余切割方式所要切割的边 \(e\) 都在该子树内,否则不满足连通块大小关系。在大小为 \(F_{n - 1}\) 的子树内切 \(e_2\),又必定将其分隔成大小为 \(F_{n - 2}\) 和 \(F_{n - 3}\) 的两部分,又因为 \(e_1,e_2\) 的切割顺序不影响答案且我们保证了大小 \(\le F_n\) 的 Fib-tree 满足条件,故先切割 \(e_2\) 也是合法的。第一步切出的是大小为 \(F_{n - 2}\) 的子树的情况同理。

故直接找到一个切割后两连通块大小合法的边进行切割,往下递归判断即可。

每次递归问题规模由 \(F_{i}\) 变为 \(F_{i - 1}\) 和 \(F_{i - 2}\),故只会递归 \(i\) 层,每个点至多被遍历 \(i\) 次,又由于 \(F_n = O(\phi^n),\phi = \dfrac{1 + \sqrt{5}}{2}\),故时间复杂度为 \(O(n\log n)\)。

想到了的地方 / 探索过的思路

划分后的两连通块大小唯一。

没想到的地方

可以任意划分。

代码

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 4e5 + 5;
vector<int> t[N];
int siz[N];
int f[N],rev[N],vis[N];
int n;
void dfs(int x,int fa){
	siz[x] = 1;
	for(int nx : t[x])if(nx != fa){
		dfs(nx,x);
		siz[x] += siz[nx];
	}
}
pii get(int x,int fa,int sz){
	if(siz[x] == sz){
		return {x,fa};
	}
	pii res = {-1e9,-1e9};
	for(int nx : t[x])if(nx != fa && !vis[nx]){
		res = max(res,get(nx,x,sz));
	}
	return res;
}
void getsz(int x,int fa){
	siz[x] = 1;
	for(int nx : t[x])if(nx != fa && !vis[nx]){
		getsz(nx,x);
		siz[x] += siz[nx];
	}
}
void vs(int x,int fa){
	vis[x] = true;
	for(int nx : t[x])if(nx != fa && !vis[nx]){
		vs(nx,x);
	}
}
bool sol(int x,int fa,int sz){
	if(x < 0) return false;
	if(sz == 1) return true;
	int k = rev[sz];
	siz[fa] = 0;
	getsz(x,fa);
	pii p1 = get(x,fa,f[k - 1]),p2 = get(x,fa,f[k - 2]);
	bool f1 = false,f2 = false;
	if(p1.first >= 1){
		f1 = sol(p1.first,p1.second,f[k - 1]),f2 = sol(x,fa,f[k - 2]);
	}else if(p2.first >= 1){
		f1 = sol(p2.first,p2.second,f[k - 2]),f2 = sol(x,fa,f[k - 1]);
	}
	vs(x,fa);
	return f1 && f2;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
	cin>>n;
	f[0] = f[1] = 1;
	for(int i=2;;i++){
		f[i] = f[i - 1] + f[i - 2];
		if(f[i] > n) break;
		rev[f[i]] = i;
	}
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		t[u].push_back(v);
		t[v].push_back(u);
	}
	bool f = sol(1,0,n);
	cout<<(f ? "YES" : "NO")<<'\n';
	return 0;
}
posted @ 2026-10-13 11:42  yutar  阅读(6)  评论(0)    收藏  举报