CF1385F Removing Leaves

CF1385F Removing Leaves

令人疑惑的 CF2300。

手玩样例后发现以下性质:

1、新叶子产生当且仅当一个点的度数变为 \(1\),唯一的特殊情况是遍历的起点(指定根)度数变为 \(1\)

2、进行贪心:自下而上,有就删,删完为止。容易证明该贪心的正确性:我们的操作不会使得某一次原本可以操作的删除由于贪心算法的操作而变得不可删除,也就是说我们的算法是不劣的。

3、当 \(k = 1\) 时,答案为 \(n - 1\)

考虑到性质 \(1\),我们有一个朴素做法:指定根,跑 \(n\) 轮 dfs 然后按性质 \(2\) 删点统计答案。时间复杂度 \(O(n^2)\)

这时有一个猜测:在根的度数变为 \(1\) 的特例中,产生的新的删除一定可以沿着一条链全部扫描到(即儿子的度数又变成 \(1\),儿子的儿子度数又变成 \(1 \dots\))。很容易发现这就是对的。

因此我们只需要随便指定一个根,先正常跑完一遍 dfs,然后再跑这条链就可以了。

时间复杂度疑似 \(O(n)\),但其实 dfs 过程新开 vector 可能带来了一些常数。

请注意本题统计答案过程中的细节。

#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 = 4e5;
int T, n, k, ans = 0;
int d[N];
bool del[N];
vector<int> G[N];
namespace task{
	void dfs(int u, int fa){
		vector<int> h;
		for(auto v : G[u]){
			if(v == fa) continue;
			++ d[u];
			dfs(v, u);
			if(d[v] == 0) h.push_back(v);
		}
		int y = h.size();
		for(int i = k - 1; i < y; i += k){
			F(j, i - k + 1, i){
				del[h[j]] = 1;	
				-- d[u];
			}
			++ ans;
		}
	}
	void Main(){
		cin >> n >> k;
		F(i, 1, n - 1){
			int u, v;
			cin >> u >> v;
			G[u].push_back(v);
			G[v].push_back(u);
		}
		dfs(1, 0);
		int x = 1;
		while(d[x] == 1){
			int y = 0, num = 0;
			for(auto v : G[x]) if(del[v] == 0){
				y = v;
				break;
			}
//			printf("%d->%d\n", x, y);
			for(auto v : G[y]){
				if(v == x || del[v] == 1 || d[v] > 0) continue;
				// is leaf
				del[v] = 1;
				-- d[y];
				++ num;
			}
			del[x] = 1;
			-- d[x]; 
			++ num; 
			
			if(num % k == 0) ++ ans; // zhen shan
			else break; // jia shan
			x = y;
		}
		cout << ans << '\n';
		F(i, 1, n){
			G[i].clear();
			d[i] = 0;
			del[i] = 0;
		}
		ans = 0;
	}
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> T;
	while(T --) task::Main();
	return fflush(0), 0;
}
posted @ 2025-08-04 15:40  superl61  阅读(6)  评论(0)    收藏  举报