【动态规划】长链剖分优化树形 dp
我们在树形 dp 中经常会遇到这样一个模型:
设
这样直接做是
有一个小优化,就是我们想让
考虑直接继承哪一个儿子呢?
对于其他儿子,我们直接循环
看似没有优化多少,这时你会发现程序很快,事实上,时间已经优化到了
考虑一个点什么时候会被暴力合并,即它是其父亲的轻儿子时,所以它就是一条长链的链顶。然而它被暴力合并需要的次数就是这条长链的长度,而这条长链下面的点都直接过继给父亲,没有暴力合并,相当于每个点在暴力合并中贡献了一次,所以只会合并
给定一棵
考虑设状态,我们设
与上面说的相符,直接长剖转移即可,对于每个节点记录最大转移点,过继重儿子的时候将转移点判断一下,再继承过来。
对于空间分配,分析上述证明过程可以发现总空间占用也是
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
vector <int> G[N];
struct Tree{
int dfn,dep,mxd,siz,fa,top,son;
}t[N];
int val[N * 2],*dp[N],*tp = val,tot = 0,mxp[N],n;
inline void dfs1(int x,int last)
{
t[x].fa = last;
t[x].dep = t[last].dep + 1;
t[x].siz = 1;
t[x].mxd = 0;
for(auto to : G[x])
{
if(to == last) continue;
dfs1(to,x);
t[x].siz += t[to].siz;
if(t[to].mxd + 1 > t[x].mxd)
{
t[x].mxd = t[to].mxd + 1;
t[x].son = to;
}
}
}
inline void dfs2(int x,int last)
{
t[x].dfn = ++tot;
if(t[x].son) {dp[t[x].son] = dp[x] + 1; t[t[x].son].top = t[x].top; dfs2(t[x].son,x);}
for(auto to : G[x])
{
if(to == last || to == t[x].son) continue;
t[to].top = to;
dp[to] = tp; tp += t[to].mxd + 1;
dfs2(to,x);
}
}
inline void dfs3(int x,int last)
{
dp[x][0] = 1; mxp[x] = 0;
if(t[x].son)
{
dfs3(t[x].son,x);
if(dp[x][mxp[t[x].son] + 1] > dp[x][mxp[x]]) mxp[x] = mxp[t[x].son] + 1;
}
for(auto to : G[x])
{
if(to == last || to == t[x].son) continue;
dfs3(to,x);
for(int i = 0;i <= t[to].mxd;i++)
{
dp[x][i + 1] += dp[to][i];
if(dp[x][i + 1] > dp[x][mxp[x]]) mxp[x] = i + 1;
else if(dp[x][i + 1] == dp[x][mxp[x]] && mxp[x] > i + 1) mxp[x] = i + 1;
}
}
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>n;
for(int i = 1,x,y;i <= n - 1;i++)
{
cin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
dfs1(1,0);
t[1].top = 1; dp[1] = tp; tp += t[1].mxd;
dfs2(1,0);
dfs3(1,0);
for(int i = 1;i <= n;i++) cout<<mxp[i]<< '\n';
return 0;
}
给一棵有
容易发现这样是三元组一定形如一个中心点到三个点的距离相等。这个中心点可以是三个点的
我们考虑统计下面的一对点,设
也可以定义为: 点对
我们发现每次
对于每一个儿子
我们考虑同样地长剖优化,将
考虑第一个转移必须要枚举一边,通过重复空间不能转移。
但是我们发现,
那么我们如何统计答案呢?
考虑 “中心点” 头上的链穿过
-
一个点在前面的子树中,两个点在子树
中: 。意思就是 中的点对还需要 的长度,算上 的边,前面子树中到 长度为 的链个数就是答案。 -
两个点在前面的子树中,一个点在
中: 。同理。
我们可以发现,合并第一个儿子时答案只有一种贡献:就是中心点到
所以过继重儿子的时候也不用统计答案,保证了复杂度为
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n,a[N],b[N],tot = 0;
vector <int> G[N];
typedef long long ll;
ll v1[N * 5],*f[N],*g[N],*tp = v1,ans = 0;
struct Node{
int dep,dfn,son,siz,mxd,fa,top;
}t[N];
inline void dfs1(int x,int last)
{
t[x].dep = t[last].dep + 1;
t[x].fa = last;
t[x].siz = 1;
t[x].mxd = 1;
for(auto to : G[x])
{
if(to == last) continue;
dfs1(to,x);
t[x].siz += t[to].siz;
if(t[to].mxd + 1 > t[x].mxd) t[x].mxd = t[to].mxd + 1,t[x].son = to;
}
}
inline void dfs2(int x,int last)
{
t[x].dfn = ++tot;
if(t[x].son) {t[t[x].son].top = t[x].top; f[t[x].son] = f[x] + 1; g[t[x].son] = g[x] - 1; dfs2(t[x].son,x); }
for(auto to : G[x])
{
if(to == last || to == t[x].son) continue;
f[to] = tp; tp += 2 * (t[to].mxd + 1);
g[to] = tp; tp += 2 * (t[to].mxd + 1);
t[to].top = to;
dfs2(to,x);
}
}
inline void dfs3(int x,int last)
{
f[x][0] = 1; g[x][0] = 0;
if(t[x].son) {dfs3(t[x].son,x);}
ans += g[x][0];
for(auto to : G[x])
{
if(to == last || to == t[x].son) continue;
dfs3(to,x);
for(int i = 1;i <= t[to].mxd;i++) ans += f[x][i - 1] * g[to][i] + g[x][i] * f[to][i - 1];
for(int i = 0;i <= t[to].mxd;i++) g[x][i + 1] += f[x][i + 1] * f[to][i];
for(int i = 0;i <= t[to].mxd;i++) f[x][i + 1] += f[to][i];
for(int i = 1;i <= t[to].mxd;i++) g[x][i - 1] += g[to][i];
}
}
int main()
{
cin>>n;
for(int i = 1,x,y;i <= n - 1;i++)
{
cin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
dfs1(1,0);
t[1].top = 1;
f[1] = tp; tp += 2 * (t[1].mxd + 1);
g[1] = tp; tp += 2 * (t[1].mxd + 1);
dfs2(1,0);
dfs3(1,0);
cout<<ans;
return 0;
}
给定
求平均值最大,考虑 0/1 分数规划,二分这个答案,将每条边权减去
我会点分!
考虑一个点
转移:
我们发现这个不能直接继承,需要加一个边,这当然可以线段树区间加,但是更简便的方法是将这个 “最大权值” 改为 “点的最大深度”。
转移变为:
这就可以用长剖直接继承。
答案怎么求呢?
其中
我们发现这实在需要一个区间
注意这个
同理,转移重儿子时仍然不需要更新
最后
由于内层要用线段树维护,时间复杂度
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
const double eps = 1e-5,inf = LONG_LONG_MAX;
struct Edge{
int v,next;
double w;
}e[N * 2];
int head[N],n,L,U,tot = 0;
double mid,ans,tmp[N];
struct Segment_Tree{
double a[N << 3];
inline void modify(int l,int r,int x,double k,int pos)
{
a[pos] = max(a[pos],k);
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) modify(l,mid,x,k,pos << 1);
else modify(mid + 1,r,x,k,pos << 1 | 1);
}
inline double query(int l,int r,int L,int R,int pos)
{
if(L > R) return -inf;
if(L <= l && r <= R) return a[pos];
int mid = (l + r) >> 1; double ret = -inf;
if(L <= mid) ret = max(ret,query(l,mid,L,R,pos << 1));
if(R > mid) ret = max(ret,query(mid + 1,r,L,R,pos << 1 | 1));
return ret;
}
inline void clear() {fill(a,a + (N << 3),-inf);}
}t;
int f[N];
struct Node{
int dfn,siz,dep,mxd,son,fa,top;
double dis;
}a[N];
inline void dfs1(int x,int last)
{
a[x].dep = a[last].dep + 1;
a[x].siz = 1;
a[x].fa = last;
a[x].mxd = 0;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last) continue;
a[to].dis = a[x].dis + e[i].w;
dfs1(to,x);
a[x].siz += a[to].siz;
if(a[to].mxd + 1 > a[x].mxd) a[x].mxd = a[to].mxd + 1,a[x].son = to;
}
}
inline void dfs2(int x,int last)
{
a[x].dfn = ++tot;
f[x] = a[x].dfn;
if(a[x].son) {a[a[x].son].top = a[x].top; dfs2(a[x].son,x);}
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last || to == a[x].son) continue;
a[to].top = to;
dfs2(to,x);
}
}
inline void add(int x,int y,int z)
{
++tot;
e[tot].v = y;
e[tot].w = z;
e[tot].next = head[x];
head[x] = tot;
++tot;
e[tot].v = x;
e[tot].w = z;
e[tot].next = head[y];
head[y] = tot;
}
inline void dfs3(int x,int last)
{
t.modify(1,N,f[x],a[x].dis - mid * a[x].dep,1);
if(a[x].son) dfs3(a[x].son,x);
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(to == last || to == a[x].son) continue;
dfs3(to,x);
for(int j = 1;j <= a[to].mxd + 1;j++)
{
double vto = t.query(1,N,f[to] + j - 1,f[to] + j - 1,1);
if(j <= U)
ans = max(ans,vto + t.query(1,N,f[x] + max(1ll,L - j),f[x] + min(U - j,a[x].mxd),1) - 2 * (a[x].dis - mid * a[x].dep));
tmp[j] = vto;
}
for(int j = 1;j <= a[to].mxd + 1;j++) t.modify(1,N,f[x] + j,tmp[j],1);
}
ans = max(ans,t.query(1,N,f[x] + L,f[x] + min(U,a[x].mxd),1) - (a[x].dis - mid * a[x].dep));
}
inline bool ck()
{
t.clear();
ans = -inf;
dfs3(1,0);
return ans >= 0;
}
signed main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>n;
cin>>L>>U;
for(int i = 1,x,y,z;i <= n - 1;i++)
{
cin>>x>>y>>z;
add(x,y,z);
}
tot = 0;
a[0].dep = -1; a[0].dis = 0;
dfs1(1,0);
dfs2(1,0);
double l = 0,r = 1e6;
while(l + eps < r)
{
mid = (l + r) / 2;
if(ck()) l = mid;
else r = mid;
}
cout<<fixed<<setprecision(3)<<l;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话