P4292 && CF150E
谔谔,点分治题。
以 P4292 为例:求出树上一条边数在 \([L,R]\) 之间的路径,使得边权和除以边数最大。
考虑在点分治过程中动态统计这个答案,即经过当前分治中心且满足条件的路径中权值最大的。
考虑判定一个答案\(x\) 是否合法,即:
\(\frac{\sum w}{|S|}\ge x\),移项得:\(\sum w-x\ge0\),即对于原来的一条边权为 \(w\) 的边,将其权值设为 \(w-x\),若存在满足条件的路径且权值和大于等于 \(0\),这个答案合法。
显然答案具有单调性,于是在点分治过程中套上一个二分,现在是两只 log,不能再多了。考虑如何在不升复杂度的前提下统计这个答案。
点分治遍历子树的过程中,记前缀桶 \(t_1\),这一棵子树的桶 \(t_2\)。对于现在这个子树的一个固定深度,合法的桶对应一个向一个方向移动的区间,于是可以滑动窗口,可以去掉一只 log。
最后是按子树深度大小从小到大合并子树,可以理解为启发式合并。最终复杂度是 \(O(n\log^2n)\)
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<vector>
#define pb push_back
using ld = double;
const int N = 1e5 + 10; const ld eps = 1e-8;
ld rs, ds[N], s1[N], s2[N]; std::vector<std::pair<int, int>> g[N];
int n, L, R, nn, rt, t, q[N], vs[N], sz[N], ms[N], d[N], pr[N], md[N];
void fd(int x, int fa){
sz[x] = 1, ms[x] = 0;
for(auto [v,w]:g[x]) if(v!=fa && !vs[v])
fd(v, x), sz[x]+=sz[v], ms[x]=std::max(ms[x],sz[v]);
if((ms[x]=std::max(ms[x],nn-sz[x]))<ms[rt]) rt=x;
}
void dfs(int x, int fa){
d[x] = d[fa] + 1; md[x] = 1;
for(auto [v,w]:g[x]) if(v!=fa && !vs[v])
dfs(v, x), pr[v] = w, md[x] = std::max(md[x], md[v]+1);
}
void gd(int x, int fa, double md){
ds[x]=ds[fa]+pr[x]-md; s2[d[x]]=std::max(s2[d[x]],ds[x]);
for(auto [v,w] : g[x]) if(v!=fa && !vs[v]) gd(v, x, md);
}
bool ck(ld x, std::vector<int> &vc){
ld rs = -2e9; for(auto v:vc){
int st = 1, ed = 0; ds[rt] = 0;
for(int i = 1; i <= md[v]; i++) s2[i] = -2e9;
gd(v, rt, x); for(int i = 0; i <= md[v]; i++){
if(st <= ed && q[st]>R-i) ++st;
if(L-i<=md[v]){while(st<=ed&&s1[q[ed]]<s1[L-i])ed--; q[++ed]=L-i;}
if(st <= ed) rs = std::max(rs, s2[i] + s1[q[st]]);
} for(int i = 1; i <= md[v]; i++) s1[i] = std::max(s1[i], s2[i]);
} return rs >= 0;
}
void sol(int x){
vs[x] = 1, d[0] = -1; dfs(x, 0), ds[x] = 0;
std::vector<int> vc; for(auto [v,w]:g[x]) if(!vs[v]) vc.pb(v);
std::sort(vc.begin(), vc.end(), [](int a, int b){return md[a]<md[b];});
t = vc.size()-1; if(t==-1) return; int mx = md[vc[t]]; ld l=rs, r=1e6;
while(l+eps<r){
ld mid=(l+r)/2; for(int i=1;i<=mx;i++) s1[i]=-2e9; s1[0]=0;
if(ck(mid, vc)) l = mid; else r = mid;
} rs = std::max(rs, l);
for(auto v:vc) rt=0, nn=sz[v], ms[0]=n, fd(v, x), sol(rt);
}
int main(){
std::cin >> n >> L >> R;
for(int i = 1, x, y, w; i < n; i++)
std::cin >> x >> y >> w, g[x].pb({y, w}), g[y].pb({x, w});
nn = ms[0] = n; fd(1, 0), sol(rt);
std::cout << std::fixed << std::setprecision(3) << rs;
}
CF150E,判断一个二分出来的中位数是否合法,把小于他的边设为 \(-1\),否则设为 \(1\)。若存在路径和大于等于 \(0\) 则合法,然后你发现这两就变成了一道题。
由于我太懒了,连改代码都不想改,于是这题我直接抄的题解。