点分治

通过不断地寻找重心,每次可以重新dfs子树收集跨根节点信息。时间复杂度 \(\mathcal{O}(n \log n)\),证明过程类似启发式合并。

形式比较一致,主要是4个函数:
1.getsz(),每次寻找重心前需要重新计算子树大小。
2.getsent(),寻找重心。
3.work(),分治。
4.dfs(),收集信息。

1.https://www.luogu.com.cn/problem/P2634

题意概述:给定一棵无根树,带边权,两个人随机选择两个节点,求这两个节点距离为3的倍数的概率。

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve(){
	int n;
	cin >> n;

	vector<vector<pair<int,int>>> adj(n+1);
	for (int i=0;i<n-1;i++){
		int u,v,w;
		cin >> u >> v >> w;

		adj[u].emplace_back(w,v);
		adj[v].emplace_back(w,u);
	}

	vector<int> sz(n+1);

	//记录每个点有没有作为过分治的重心
	vector<bool> vis(n+1,false);

	vector<ll> dp(3,0),cur(3,0);
	ll res = 0;

	function<void(int,int)> getsz = [&](int u,int par){
		sz[u] = 1;

		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			getsz(v,u);
			sz[u] += sz[v];
		}
	};


	auto getcent = [&](int u){
		//寻找重心前需重新计算size信息
		getsz(u,-1);
		int half = sz[u]/2;
		int par = -1;

		//迭代寻找
		while (1){
			bool ok = true;

			for (auto& [w,v]:adj[u]){
				if (v==par || vis[v]) continue;

				if (sz[v]>half){
					par = u;
					u = v;
					ok = false;
					break;
				}
			}

			if (ok){
				break;
			}
		}

		return u;
	};

	function<void(int,int,int)> dfs = [&](int u,int par,int dis){
		cur[dis%3]++;

		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			dfs(v,u,(dis+w)%3);
		} 
	};

	//u为重心,进行分治,维护跨u的信息
	function<void(int)> work = [&](int u){
		vis[u] = true;
		dp[0] = dp[1] = dp[2] = 0;
		res++;

		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;

			cur[0] = cur[1] = cur[2] = 0;
			dfs(v,u,w);

			res += dp[0]*cur[0]*2+dp[1]*cur[2]*2+dp[2]*cur[1]*2+cur[0]*2;
			dp[0] += cur[0];
			dp[1] += cur[1];
			dp[2] += cur[2];
		}

		//继续分治其他点
		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;

			work(getcent(v));
		}
	};


	work(getcent(1));
	
	ll p = res;
	ll q = (ll)n*n;
	ll g = __gcd(p,q);

	cout << p/g << '/' << q/g << '\n';	
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	// cin >> t;
	while (t--) solve();

	return 0;
}

2.https://www.luogu.com.cn/problem/P3806

题意概述:给定一棵无根树,带边权,m次询问,查询是否存在距离为k的路径。

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve(){
	int n,m;
	cin >> n >> m;

	vector<vector<pair<int,int>>> adj(n+1);
	for (int i=0;i<n-1;i++){
		int u,v,w;
		cin >> u >> v >> w;

		adj[u].emplace_back(w,v);
		adj[v].emplace_back(w,u);
	}	


	vector<int> sz(n+1);
	vector<bool> vis(n+1,false);
	map<int,bool> que;
	set<int> dp,cur;

	vector<int> vq(m);
	for (int i=0;i<m;i++){
		cin >> vq[i];
		que[vq[i]] = false;
	}


	function<void(int,int)> getsz = [&](int u,int par){
		sz[u] = 1;

		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			getsz(v,u);
			sz[u] += sz[v];
		}
	};

	auto getcent = [&](int u){
		getsz(u,-1);
		int par = -1;
		int half = sz[u]/2;

		while (1){
			bool ok = true;

			for (auto& [w,v]:adj[u]){
				if (v==par || vis[v]) continue;

				if (sz[v]>half){
					ok = false;
					par = u;
					u = v;
					break;
				}
			}

			if (ok) break;
		}

		return u;
	};


	function<void(int,int,int)> dfs = [&](int u,int par,int dis){
		cur.insert(dis);
		
		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			dfs(v,u,dis+w); 
		}	
	};

	function<void(int)> work = [&](int u){
		vis[u] = true;
		dp.clear();

		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;
			cur.clear();

			dfs(v,u,w);

			for (auto& val:cur){
				for (auto& [q,f]:que){
					if (dp.count(q-val) || cur.count(q)){
						f = true;
					}
				}
			}

			for (auto& val:cur){
				dp.insert(val);
			}
		}

		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;

			work(getcent(v));
		}
	};

	work(getcent(1));

	for (int i=0;i<m;i++){
		if (que[vq[i]]){
			cout << "AYE" << '\n';
		}
		else{
			cout << "NAY" << '\n';
		}
	}

}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	// cin >> t;
	while (t--) solve();

	return 0;
}

3.https://www.luogu.com.cn/problem/P4149

题意概述:给一棵树,每条边有权。求一条简单路径,权值和等于 k,且边的数量最小。

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int INF = 1e9;

void solve(){
	int n,k;
	cin >> n >> k;

	vector<vector<pair<ll,int>>> adj(n+1);
	for (int i=0;i<n-1;i++){
		int u,v,w;
		cin >> u >> v >> w;
		u++,v++;

		adj[u].emplace_back(w,v);
		adj[v].emplace_back(w,u);
	}


	vector<int> sz(n+1);
	vector<bool> vis(n+1,false);
	int res = INF;
	map<ll,int> dp,cur;


	function<void(int,int)> getsz = [&](int u,int par){
		sz[u] = 1;

		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			getsz(v,u);
			sz[u] += sz[v];
		}
	};

	auto getsent = [&](int u){
		getsz(u,-1);
		int par = -1;
		int half = sz[u] >> 1;

		while (1){
			bool ok = true;

			for (auto& [w,v]:adj[u]){
				if (v==par || vis[v]) continue;

				if (sz[v]>half){
					ok = false;
					par = u;
					u = v;
					break;
				}
			}

			if (ok){
				break;
			}
		}

		return u;
	};

	function<void(int,int,ll,int)> dfs = [&](int u,int par,ll dis,int ed){
		if (dis>k) return;

		if (!cur.count(dis)){
			cur[dis] = ed;
		}
		else{
			cur[dis] = min(cur[dis],ed);
		}

		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			dfs(v,u,dis+w,ed+1);
		}
	};

	function<void(int)> work = [&](int u){
		vis[u] = true;
		dp.clear();

		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;

			cur.clear();
			dfs(v,u,w,1);

			if (cur.count(k)){
				res = min(res,cur[k]);
			}

			for (auto it=cur.begin();it!=cur.end();it++){
				if (it->first==k) continue;

				if (dp.count(k-it->first)){
					res = min(res,dp[k-it->first]+it->second);
				}
			}

			for (auto it=cur.begin();it!=cur.end();it++){
				if (!dp.count(it->first)){
					dp[it->first] = it->second;
				}
				else{
					dp[it->first] = min(dp[it->first],it->second);
				}
			}
		}

		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;

			work(getsent(v));
		}
	};

	work(getsent(1));

	if (res==INF){
		cout << -1 << '\n';
	}
	else{
		cout << res << '\n';
	}
}	

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	// cin >> t;
	while (t--) solve();

	return 0;
}

4.https://www.luogu.com.cn/problem/P4178

题意概述:给定一棵树,每条边有边权,求出树上两点距离小于等于 k 的点对数量。

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve(){
	int n;
	cin >> n;

	vector<vector<pair<int,int>>> adj(n+1);
	for (int i=0;i<n-1;i++){
		int u,v,w;
		cin >> u >> v >> w;

		adj[u].emplace_back(w,v);
		adj[v].emplace_back(w,u);
	}

	int k;
	cin >> k;

	vector<int> sz(n+1);
	vector<bool> vis(n+1,false);
	ll res = 0;
	map<int,ll> dp,cur;

	function<void(int,int)> getsz = [&](int u,int par){
		sz[u] = 1;

		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			getsz(v,u);
			sz[u] += sz[v];
		}
	};

	auto getsent = [&](int u){
		getsz(u,-1);
		int par = -1;
		int half = sz[u] >> 1;

		while (1){
			bool ok = true;
			for (auto& [w,v]:adj[u]){
				if (v==par || vis[v]) continue;

				if (sz[v]>half){
					ok = false;
					par = u;
					u = v;
					break;
				}
			}

			if (ok){
				break;
			}
		}

		return u;
	};	


	function<void(int,int,int)> dfs = [&](int u,int par,int dis){
		if (dis>k) return;

		cur[dis]++;

		for (auto& [w,v]:adj[u]){
			if (v==par || vis[v]) continue;

			dfs(v,u,dis+w);
		}
	};


	function<void(int)> work = [&](int u){
		vis[u] = true;
		dp.clear();

		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;

			cur.clear();
			dfs(v,u,w);

			int m = dp.size();
			vector<int> a(m+1);
			vector<ll> pre(m+1,0);
			int i = 1;

			for (auto it=dp.begin();it!=dp.end();it++){
				pre[i] = pre[i-1]+it->second;
				a[i] = it->first;
				i++;
			}

			i = m;

			for (auto it=cur.begin();it!=cur.end();it++){
				while (i>=1 && it->first+a[i]>k){
					i--;
				}

				if (i>=1){
					res += pre[i]*it->second;
				}
				else{
					break;
				}
			}

			for (auto it=cur.begin();it!=cur.end();it++){
				if (it->first<=k){
					res += it->second;
				}

				dp[it->first] += it->second;
			}
		}

		for (auto& [w,v]:adj[u]){
			if (vis[v]) continue;

			work(getsent(v));
		}
	};

	work(getsent(1));

	cout << res << '\n';
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	// cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-04-13 12:01  kzssCCC  阅读(9)  评论(0)    收藏  举报