长链剖分优化 dp 学习笔记
定义:
- 重儿子:子树内深度最大值最大的儿子。
- 重边:连向重儿子的边。
- 重链:若干条相邻重边连接的及其端点的一条链,并且是极长的。
长剖优化 dp 一般都是 dp 式子有一维和深度或距离相关的,于是可以在处理时优先递归重儿子并继承其 dp 值,然后遍历其轻儿子。
因为每条重链都只在其链顶的父亲处被遍历到,而所有重链的长度之和为 \(n\),所以复杂度为 \(O(n)\)。
用长剖是因为我们有时会把信息保存在其 dfs 序上,如果保存轻儿子则可能出现重儿子的 dp 序列比这个儿子要长就合并不了。
P4292 [WC2010] 重建计划
长剖优化 dp。
看见分式先套一层二分,求答案是否能 \(\ge mid\)。路径长度都减去 \(mid\)。现在变成求是否存在一条长度在 \([L,U]\) 之间的路径其路径长度 \(\ge 0\)。
设 \(f_{u,x}\) 为点 \(u\) 向下走 \(x\) 条边的最长距离。
重儿子暴力合并,轻儿子合并的时候记录一下答案即可。
记录答案考虑线段树维护 \(f\),可以维护到其 dfs 序然后用线段树上这样不用每次清空,然后每次合并一个轻儿子 \(f_{v,x}\) 就查一下区间最值更新答案即可。
#include<bits/stdc++.h>
#define sd std::
#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
#define inf 1e13
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=5e5+10,P=1e9+7;
const double eps=1e-5;
int n,dfn[N],num,son[N],dep[N],W[N],up,down;//上下界
sd vector<pii> G[N];
double s[N<<2],lazy[N<<2];//区间维护最大值
#define ls k<<1
#define rs k<<1|1
void clear(int k,int l,int r)
{
s[k]=lazy[k]=0;
if(l==r) return;
int mid=l+r>>1;
clear(ls,l,mid);
clear(rs,mid+1,r);
}
void pushdown(int k)
{
if(!lazy[k]) return;
s[ls]+=lazy[k];
s[rs]+=lazy[k];
lazy[ls]+=lazy[k];
lazy[rs]+=lazy[k];
lazy[k]=0;
}
void update(int k,int l,int r,int x,int y,double v)//区间加
{
if(x<=l&&y>=r)
{
s[k]+=v;
lazy[k]+=v;
return;
}
pushdown(k);
int mid=l+r>>1;
if(x<=mid) update(ls,l,mid,x,y,v);
if(y>mid) update(rs,mid+1,r,x,y,v);
s[k]=sd max(s[ls],s[rs]);
}
double ask(int k,int l,int r,int x,int y)
{
if(x<=l&&y>=r) return s[k];
pushdown(k);
int mid=l+r>>1;
double res=-inf;
if(x<=mid) res=sd max(res,ask(ls,l,mid,x,y));
if(y>mid) res=sd max(res,ask(rs,mid+1,r,x,y));
return res;
}
void dfs1(int u,int fa)//dep[u]为u最多能向下走几条边
{
dep[u]=0;
for(auto [v,w]:G[u])
{
if(v==fa) continue;
dfs1(v,u);
if(!son[u]||dep[v]>dep[son[u]]) son[u]=v,W[u]=w;
dep[u]=sd max(dep[v]+1,dep[u]);
}
}
void dfs2(int u,int fa)
{
dfn[u]=++num;
if(son[u]) dfs2(son[u],u);
for(auto [v,w]:G[u]) if(v!=fa&&v!=son[u]) dfs2(v,u);
}
double mid,val;
void dp(int u,int fa)
{
if(son[u])
{
dp(son[u],u);
// dg(u);
// dbg(dfn[u]+1),dbg(dfn[u]+dep[u]),sd cout<<1.0*W[u]-mid<<"\n";
update(1,1,n,dfn[u]+1,dfn[u]+dep[u],1.0*W[u]-mid);
int L=dfn[u]+down,R=dfn[u]+sd min(dep[u],up);
// dbg(L),dg(R);
if(L<=R)
{
val=sd max(val,ask(1,1,n,L,R));
// dg(u);
// sd cout<<val<<"\n";
}
}
for(auto [v,w]:G[u])
{
if(v==fa||v==son[u]) continue;
dp(v,u);
F(i,dfn[v],dfn[v]+dep[v])//先计算答案
{
int cnt=i-dfn[v]+1;//实际边数
// dbg(i);
// dg(cnt);
int L=sd max(0ll,down-cnt),R=sd min(dep[u],up-cnt);
// dbg(L),dg(R);
if(L>R) continue;
L+=dfn[u],R+=dfn[u];
val=sd max(val,ask(1,1,n,i,i)+ask(1,1,n,L,R)+w-mid);
}
F(i,dfn[v],dfn[v]+dep[v])//合并
{
int cnt=i-dfn[v]+1;
double c=(ask(1,1,n,i,i)+w-mid)-ask(1,1,n,dfn[u]+cnt,dfn[u]+cnt);
if(c>eps) update(1,1,n,dfn[u]+cnt,dfn[u]+cnt,c);
}
}
}
int check()
{
val=-inf;
clear(1,1,n);
dp(1,0);
// puts("val:");
// sd cout<<val<<"\n";
return val>=0;
}
void solve()
{
n=read();
down=read(),up=read();
F(i,2,n)
{
int x=read(),y=read(),z=read();
G[x].emplace_back(y,z);
G[y].emplace_back(x,z);
}
dfs1(1,0);
dfs2(1,0);
// F(i,1,n) printk(dep[i]),put(dfn[i]);
// puts("");
// put(son[1]);
// mid=5;
// put(check());
double l=0,r=1e6,ans=0;
while(r-l>eps)
{
clear(1,1,n);
mid=(l+r)/2;
if(check()) ans=mid,l=mid;
else r=mid;
}
printf("%.3lf",ans);
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T=1;
// T=read();
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号