首先,重链剖分我们有所认识,在dsu on tree和数据结构维护链时我们都用过他的性质。

  在这里,我们要介绍一种新的剖分方式,我们求出这个点到子树中的最长链长,这个链长最终从哪个儿子更新而来,那个儿子就是所谓的“重儿子”,也可以叫长儿子。

  我们的做法就是,在统计一个点的信息时,对于重儿子,我们直O(1)接继承它的答案(这里有指针技巧,只能看代码,不可言传),对于轻儿子我们暴力统计。

  复杂度分析:一个点被计算,最多只会在作为重链上的点时被继承一次,在重链顶端时被暴力统计一次。所以最终复杂度是O(N)的。

  因为我们这里要谈的是dp优化,所以我们还没有必要研究这个结构的性质。

  它有两个应用,首先就是优化以链长度为下标的树形dp,也就是今天我们要谈的玩法,还有一个是快速求一个点的k级祖先,这个我们先不研究。

  只凭语言大家很难体会到这个算法的难度,下面我们看一些题目。

首先是CF1009:

  这道题完全可以用dsu on tree的科技过去,但是为了能入手一道简单的长剖题目,我们还是思考一下。

  如果设计一个dp:dp[i][j]表示以i为根的子树内离i距离为j的节点个数。转移方程也就很好写了:dp[x][j]+=dp[y][j-1]。(y是x的儿子),我们观察,在继承一个儿子的答案时,儿子的数组整体左移一个元素的位置可以直接贡献给父亲,于是我们就做到了O(1)继承。

  于是暴力统计其他儿子的时候我们直接按方程转移即可。

 代码:

 //倔强芬芳了惘然
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int N=;
struct node{int y,nxt;}e[N*];
int n,m,a[N],d[N],fa[N],son[N],h[N];
int ans[N],cnt[N],c,st[N],tt;
void add(int x,int y){
e[++c]=(node){y,h[x]};h[x]=c;
e[++c]=(node){x,h[y]};h[y]=c;
} void dfs(int x){ d[x]=;
for(int i=h[x],y;i;i=e[i].nxt)
if((y=e[i].y)!=fa[x]){
fa[y]=x;dfs(y);d[x]=max(d[x],d[y]+);
if(d[y]>d[son[x]]) son[x]=y;
} return ;
} void solve(int x){
int *f=&cnt[st[x]=++tt],*g;
f[ans[x]=]=;
if(son[x]) solve(son[x]),
ans[x]=ans[son[x]]+;else return ;
if(ans[x]==) ans[x]=;
for(int i=h[x],y;i;i=e[i].nxt)
if((y=e[i].y)!=fa[x]&&y!=son[x]){
solve(y);g=&cnt[st[y]];
for(int j=;j<=d[y]-;j++)
if((f[j+]+=g[j])>=f[ans[x]]&&j+<ans[x]||
f[j+]>f[ans[x]]) ans[x]=j+;
} return ;
} void solve(){
dfs();solve();
for(int i=;i<=n;i++)
printf("%d\n",ans[i]);
} int main(){
scanf("%d",&n);
for(int i=,x,y;i<n;i++)
scanf("%d%d",&x,&y),add(x,y);
solve();return ;
}

长链剖分

现在是POI2014Hotels

  其实大部分人对计数题还是有一定抵触的,因为一些做法的正确性很难把握。dp是很常用的计数手段,但是这个题的dp方程很有意思。向各位推荐一篇题解→luogu题解1

  我们只借用它的方程考虑这个能不能直接O(1)继承重儿子的答案?(当然可以啦)

  但是我们注意,f数组和g数组在继承的时候方向是不一样的,因为这一点,我们最好在递归之前就为下面的计算分配好指针,来保证顺利继承,另外,在空间分配上,这个题也很巧妙。因为我们在长链上,f数组不断向后偏移,g数组不断向前偏移,所以我们要为每段数组预留出两个链长的空间,很难描述,还是要去研究代码来理解这种分配规则。可以说这是一道不看题解不好做的题目。

 #include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=;
struct node{int y,nxt;}e[N*];
int h[N],d[N],son[N],c,n,m,k,p;
ll tmp[N*],*id=tmp,*f[N],*g[N],ans=;
void add(int x,int y){
e[++c]=(node){y,h[x]};h[x]=c;
e[++c]=(node){x,h[y]};h[y]=c;
} void dfs(int x,int fa){
d[x]=;for(int i=h[x],y;i;i=e[i].nxt)
if((y=e[i].y)!=fa){
dfs(y,x);d[x]=max(d[x],d[y]+);
if(d[y]>d[son[x]]) son[x]=y;
} return ;
} void solve(int x,int fa){
if(son[x]) f[son[x]]=f[x]+,
g[son[x]]=g[x]-,solve(son[x],x);
f[x][]=;
for(int i=h[x],y;i;i=e[i].nxt)
if((y=e[i].y)!=fa&&y!=son[x]){
f[y]=id;id+=d[y]*;g[y]=id;
id+=d[y]*;solve(y,x);
for(int j=;j<d[y];j++){
if(j) ans+=(f[x][j-]*g[y][j]);
ans+=(f[y][j]*g[x][j+]);
} for(int j=;j<d[y];j++){
if(j) g[x][j-]+=g[y][j];
g[x][j+]+=f[x][j+]*f[y][j];
f[x][j+]+=f[y][j];
}
} ans+=g[x][];return ;
} int main(){
scanf("%d",&n);
for(int i=,x,y;i<n;i++)
scanf("%d%d",&x,&y),add(x,y);
dfs(,);f[]=id;id+=d[]*;g[]=id;id+=d[]*;
solve(,);printf("%lld\n",ans);return ;
}

长链剖分

接下来是WC2010重建计划

  其实这道题可以说是点分治界的一道神题,可是用长剖也可以做,但是并不是特别主流的做法。这个如果我们dp出局部的答案,还是需要对一个区间的状态取最优的,所以我们想到了用线段树来记状态,区间取max直接维护就好,然后需要继承一些东西的时候,我们不能用指针轻易的完成这个操作了,所以我们只好借助dfs序搞出偏移量即可。

  为什么把这道题放在这个位置,首先因为它综合了其他算法,此外还是因为他的细节很多,容易手残写错,可以献给大家练习代码能力。(我的代码不知道出了什么鬼,就是不能开O2,一开O2就全T要么就全RE,不过比点分治短就是了)

代码:

 #include<bits/stdc++.h>
#define db double
using namespace std;
const int N=;
struct node{int y,z,nxt;}e[N];
int L,U,n,son[N];double p,f[N],g[N],ans;
int h[N],ww[N],d[N],pos[N],tot,c,rt,cnt,lm;
struct segt{int l,r,ls,rs;db s;}t[N*];
void add(int x,int y,int z){
e[++c]=(node){y,z,h[x]};h[x]=c;
e[++c]=(node){x,z,h[y]};h[y]=c;
} void pushup(int x){
int ls=t[x].ls,rs=t[x].rs;
t[x].s=max(t[ls].s,t[rs].s);
} void build(int x,int l,int r){
if(l==r){t[x]=(segt){l,r,-,-,-1e10};return ;}
int mid=l+r>>;t[x].l=l;t[x].r=r;
t[x].ls=++cnt;t[x].rs=++cnt;
build(t[x].ls,l,mid);build(t[x].rs,mid+,r);
} void clear(int x){
t[x].s=1e-;
if(~t[x].ls) clear(t[x].ls);
if(~t[x].rs) clear(t[x].rs);
} db update(int x,int k,db c){
if(t[x].r==t[x].l) return t[x].s=max(t[x].s,c);
int mid=t[x].l+t[x].r>>;
if(k<=mid) update(t[x].ls,k,c);
else update(t[x].rs,k,c);pushup(x);
} db query(int x,int l,int r){
if(l<=t[x].l&&t[x].r<=r)
return t[x].s;db re=-1e18;
int mid=t[x].l+t[x].r>>;
if(l<=mid) re=max(re,query(t[x].ls,l,r));
if(mid<r) re=max(re,query(t[x].rs,l,r));
return re;
} void dfs(int x,int fa,int v){
d[x]=;for(int i=h[x],y;i;i=e[i].nxt)
if((y=e[i].y)!=fa){
dfs(y,x,e[i].z);
d[x]=max(d[x],d[y]+);
if(d[y]>d[son[x]])
son[x]=y,ww[x]=e[i].z;
} return ;
} void solve(int x,int fa){
if(!pos[x]) pos[x]=++tot;
int u=pos[x];g[u]=f[u]=;//u是x在dfs序中的位置
if(son[x]) solve(son[x],x),//v是y在dfs序中的位置
g[u]+=g[u+]+ww[x]-p,f[u]=-g[u];
update(rt,u,f[u]);
for(int i=h[x],y;i;i=e[i].nxt)
if((y=e[i].y)!=fa&&y!=son[x]){
solve(y,x);int v=pos[y],z=e[i].z;
for(int j=;j<=d[y];j++)
if(L-j<d[x]){
db q=query(rt,u+max(,L-j),
u+min(U-j,d[x]-));
ans=max(ans,z-p+f[v+j-]+g[v]+g[u]+q);
} for(int j=;j<=d[y];j++)
if(z-p+f[v+j-]+g[v]>g[u]+f[u+j])
f[u+j]=z-p+f[v+j-]+g[v]-g[u],
update(rt,u+j,f[u+j]);
} if(d[x]->=L) ans=max(ans,g[u]+
query(rt,u+L,u+min(U,d[x]-)));
} bool pd(db x){
clear(rt);p=x;
ans=-1e18;solve(,);
return ans>=;
} int main(){ rt=++cnt;
scanf("%d%d%d",&n,&L,&U);build(rt,,n);
for(int i=,x,y,z;i<n;i++)
scanf("%d%d%d",&x,&y,&z),
add(x,y,z),lm=max(lm,z);
dfs(,,);db l=,r=lm;
while(r-l>1e-){
db mid=(l+r)/2.0;
if(pd(mid)) l=mid;
else r=mid;
} printf("%.3lf\n",l);return ;
}

长链剖分

这种算法我们就讨论到这里,其实还有不少其他的题目,希望大家有余力可以多加练习。

长链剖分优化dp三例题的更多相关文章

  1. 【CF1009F】Dominant Indices(长链剖分优化DP)

    点此看题面 大致题意: 设\(d(x,y)\)表示\(x\)子树内到\(x\)距离为\(y\)的点的个数,对于每个\(x\),求满足\(d(x,y)\)最大的最小的\(y\). 暴力\(DP\) 首先 ...

  2. CF1009F Dominant Indices——长链剖分优化DP

    原题链接 \(EDU\)出一道长链剖分优化\(dp\)裸题? 简化版题意 问你每个点的子树中与它距离为多少的点的数量最多,如果有多解,最小化距离 思路 方法1. 用\(dsu\ on\ tree\)做 ...

  3. 2019.01.19 bzoj3653: 谈笑风生(长链剖分优化dp)

    传送门 长链剖分优化dpdpdp水题. 题意简述:给一棵树,mmm次询问,每次给一个点aaa和一个值kkk,询问满足如下条件的三元组(a,b,c)(a,b,c)(a,b,c)的个数. a,b是c的祖先 ...

  4. 2018.11.03 NOIP模拟 树(长链剖分优化dp)

    传送门 考虑直接推式子不用优化怎么做. 显然每一个二进制位分开计算贡献就行. 即记录fi,jf_{i,j}fi,j​表示距离iii这个点不超过jjj的点的每个二进制位的0/10/10/1个数. 但直接 ...

  5. BZOJ4543[POI2014]Hotel加强版——长链剖分+树形DP

    题意参见BZOJ3522 n<=100000 数据范围增强了,显然之前的转移方程不行了,那么不妨换一种. 因为不能枚举根来换根DP,那么我们描述的DP方程每个点要计算三个点都在这个点的子树内的方 ...

  6. 长链剖分优化树形DP总结

    长链剖分 规定若\(x\)为叶结点,则\(len[x]=1\). 否则定义\(preferredchild[x]\)(以下简称\(pc[x]\),称\(pc[x]\)为\(x\)的长儿子)为\(x\) ...

  7. 蒟蒻的长链剖分学习笔记(例题:HOTEL加强版、重建计划)

    长链剖分学习笔记 说到树的链剖,大多数人都会首先想到重链剖分.的确,目前重链剖分在OI中有更加多样化的应用,但它大多时候是替代不了长链剖分的. 重链剖分是把size最大的儿子当成重儿子,顾名思义长链剖 ...

  8. BZOJ.4543.[POI2014]Hotel加强版(长链剖分 树形DP)

    题目链接 弱化版:https://www.cnblogs.com/SovietPower/p/8663817.html. 令\(f[x][i]\)表示\(x\)的子树中深度为\(i\)的点的个数,\( ...

  9. bzoj4543 [POI2014]Hotel加强版 长链剖分+树形DP

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=4543 题解 这道题的弱化版 bzoj3522 [POI2014]Hotel 的做法有好几种吧. ...

随机推荐

  1. Mol Cell Proteomics. |廖文丽| 阿尔兹海默症临床前期的脑脊液中突触蛋白的变化先于神经变性标志物

    大家好,本周分享的是发表在Molecular & Cellular Proteomics. 上的一篇关于阿尔兹海默病临床前期生物标志物鉴定的文章,题目是Changes in synaptic ...

  2. 【渗透测试】如何使用burpsuite对特殊密码进行爆破

    爆破是渗透测试中必不可少的一部分,对于没有太大价值可利用的漏洞或是业务只有一个登陆页面时,爆破更是我们的最合适的选择.那么在爆破时,抛去目标系统对爆破频率的限制,如果遇到较为复杂的密码,该如何顺利进行 ...

  3. shell 截取字符串(转)

    linux中对字符串的处理: 1.字符串分割例如  AAAAA-BBBBBB  按-分割去前后两部分 cut : [rich@localhost ~]$ str=AAAAA-BBBBBB[rich@l ...

  4. saltstack_API接口

    1 介绍 saltstack部署完后,要和master交互,就需要使用/usr/bin/salt这个工具.but..我不想手动到salt服务器上去执行这个命令,而是让web工具来调用salt. sal ...

  5. python 基础(十二) 图片简单处理

    pillow 图片处理模块 安装 pip install pillow  pip是安装第三方模块的工具 缩放图片实例 from PIL import Image path = r'C:\Users\x ...

  6. python 基础(四) 函数

    函数 一.什么是函数? 函数是可以实现一些特定功能的 小方法 或者是小程序 优点: 提高 了代码的后期维护 增加了代码的重复使用率 减少了代码量 提高了代码可读性 二.函数的定义 使用 def关键+函 ...

  7. G-华华对月月的忠诚

    链接:https://ac.nowcoder.com/acm/contest/392/G 题意: 月月要参加学校的信息学集训,晚上不能陪华华聊天了.不过为了防止华华去和别的小姐姐聊天,浪费时间影响学习 ...

  8. 【aspnetcore】模拟中间件处理请求的管道

    几个核心对象: ApplicationBuilder 就是startup->Configure方法的第一个参数,请求(HttpContext) 就是由这个类来处理的 HttpContext 这个 ...

  9. Springboot2.X集成Quartz集群

    为什么要使用Quzrtz集群 在项目进行集群部署时,如果业务在执行中存在互斥关系,没有对定时任务进行统一管理,就会引起业务的多次执行,不能满足业务要求.这时就需要对任务进行管理,要保证一笔业务在所有的 ...

  10. ES-Mac OS环境搭建(2)

    下载 进入官网,选择downloads进入下载页. 选择elasticsaerch下载. 新的页面中,下拉选择历史版本. 下拉选择elasticsearch和版本,然后点击下载. 选择MACOS/LI ...