点分治
通过不断地寻找重心,每次可以重新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;
}

浙公网安备 33010602011771号