题面

思路

首先,可以有一个$dp$的思路

不难发现本题中,三个点如果互相距离相同,那么一定有一个“中心点”到三个点的距离都相同

那么我们可以把本题转化计算以每个点为根的情况下,从三个不同的子树中选择深度相同的三个点的方案数

进一步,我们选定1号点为根,这样定义我们的$dp$方程:

$f[u][dis]$表示以$u$为根的子树中,和$u$的距离为$dis$的点的数量

$g[u][dis]$表示以$u$为根的子树中已经选择了不属于同一个$u$的儿子子树的两个点,并且距离为dis的方案数

那么转移方程如下:

$f[u][i]=\sum_{v=son(u)}f[v][i-1]$

$g[u][i]=\sum_{v=son(u)}g[v][i+1]+\sum_{p=son(u)}\sum_{q=son(u),q\neq p} f[p][i-1]*f[q][i-1]$

这个过程非常方便$dp$,但是时间复杂度和空间复杂度都是$O(n^2)$的

然后我们惊喜地发现一件事情:

这里的$f,g$都可以先继承一个儿子的内容,然后再和别的儿子合并!

考虑我们继承的这个过程,也就是$u$从确定的继承者$v$那里获得信息的过程:

$f[u][i]=f[v][i-1]$

$g[u][i]=g[v][i+1]$

我们又惊喜地发现,如果我们一整条链都用这样的继承方式的话,那么长度为$n$的链只需要$O(n)$的空间!

具体而言,我们对这$n$个点开两个数组,$F,G$

此时,最下层的叶子最深,它的$f,g$数组的第二位只有0

那么$f[leaf][0]=F[1],g[leaf][0]=G[n]$

然后$f[fa[leaf]][0]=F[2],f[fa[leaf]][1]=F[1]$,$g$也类似

就这样一直下去

可以看到实际上$F[leaf]$保存了$n$个$dp$值,因为这$n$个是相等的

让后我们又又惊喜地发现,现在我们的$dp$,可以逐个子树做合并,每一次合并的复杂度是$maxdep(v)$

这样我们就得到了一个时间复杂度$O(n)$,空间复杂度$O(n)$的算法

PS.本题中其实什么剖分方法都是一样的qwq,都是$O(n)$

Summary

个人认为这道题是一道好题

其实,原本的$dp$思路并不难想,同时后面的做优化的原因其实都已经很好地植入在上一层的方法里面了

也就是说,这道题如果你的思维开阔,是很容易顺着思路一路就从$O(n2)+O(n2)$优化到$O(n)+O(n)$的

本题中体现的主要思想,在于观察已有方法中的一些性质,并代入一些非常规手法(启发式合并和一个位置保存多个$dp$值)

这和之前我曾经遇到的一些题目不同

大部分题目是在旧方法里面找时间复杂度高的部分,然后以这些部分为主要攻关点来进行突破、优化

然而当我们遇到影响复杂度的地方并不集中的算法时,我们就需要打开思路,从算法本身性质入手,套用更优秀的方法来解题

这道题并不是一道“套路题”,不是只要做过类似的东西就能一下子秒掉的

现在的OI考场上,我们遇到的这类题目也一定会越来越多——这实际上意味着OI比赛题目质量的整体上升

因此我们一定要锻炼自己打开思路、广撒网的能力,不要老是一条路走到黑(就像我以前想题一样)

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define fpos DEEP_DARK_FANTASY_1
#define gpos DEEP_DARK_FANTASY_2
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,first[50010],cnte;
struct edge{
    int to,next;
}a[100010];
inline void add(int u,int v){
    a[++cnte]=(edge){v,first[u]};first[u]=cnte;
    a[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
int dep[50010],maxdep[50010],fpos[50010],gpos[50010],cntf,cntg,son[50010],siz[50010],top[50010];
ll f[200010],g[200010];
int getf(int u,int i){
    return fpos[top[u]]+dep[u]-dep[top[u]]+i;
}
int getg(int u,int i){
    return gpos[top[u]]-dep[u]+dep[top[u]]+i;
}
int getlen(int u){
    return maxdep[u]-dep[u]+1;
}
int fa[50010];
void dfs1(int u,int f){
    int i,v;fa[u]=f;
    dep[u]=dep[f]+1;maxdep[u]=dep[u];
    siz[u]=1;son[u]=0;
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;if(v==f) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[son[u]]<siz[v]) son[u]=v;
        maxdep[u]=max(maxdep[u],maxdep[v]+1);
    }
}
void dfs2(int u,int t){
    int i,v;
    top[u]=t;
    if(u==t){
        int len=getlen(u);
        fpos[u]=cntf;//这里记录的是链头,每个链头都分配一段数组,长度等于这条链的长度
        gpos[u]=cntg+len;//后来发现本题中什么剖法都是O(n),所以我写了重链剖分
        cntf+=len;
        cntg+=len<<1;
    }
    if(!son[u]) return;
    dfs2(son[u],t);
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;if(v==son[u]||v==fa[u]) continue;
        dfs2(v,v);
    }
}
ll ans=0;
void dfs(int u){
    f[getf(u,0)]=1;
    if(son[u]){
        dfs(son[u]);
        ans+=g[getg(u,0)];
    }
    int i,v,j,lim;
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;if(v==fa[u]||v==son[u]) continue;
        dfs(v);lim=getlen(v);
        for(j=1;j<=lim;j++) ans+=g[getg(u,j)]*f[getf(v,j-1)];
        for(j=1;j<=lim-1;j++) ans+=g[getg(v,j)]*f[getf(u,j-1)];
        for(j=1;j<=lim;j++) g[getg(u,j)]+=f[getf(u,j)]*f[getf(v,j-1)];
        for(j=0;j<=lim-2;j++) g[getg(u,j)]+=g[getg(v,j+1)];
        for(j=1;j<=lim;j++) f[getf(u,j)]+=f[getf(v,j-1)];
    }
}
void init(){
    memset(first,-1,sizeof(first));
    cnte=ans=0;cntf=cntg=1;
    memset(f,0,sizeof(f));memset(g,0,sizeof(g));
    memset(dep,0,sizeof(dep));memset(maxdep,0,sizeof(maxdep));
}
int main(){
    int i,t1,t2;
    while((n=read())){
        init();
        for(i=1;i<n;i++){
            t1=read();t2=read();
            add(t1,t2);
        }
        dfs1(1,0);dfs2(1,1);
        dfs(1);
        printf("%lld\n",ans);
    }
}

thr [树链剖分+dp]的更多相关文章

  1. bzoj2325 [ZJOI2011]道馆之战 树链剖分+DP+类线段树最大字段和

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=2325 题解 可以参考线段树动态维护最大子段和的做法. 对于线段树上每个节点 \(o\),维护 ...

  2. Tsinsen A1219. 采矿(陈许旻) (树链剖分,线段树 + DP)

    [题目链接] http://www.tsinsen.com/A1219 [题意] 给定一棵树,a[u][i]代表u结点分配i人的收益,可以随时改变a[u],查询(u,v)代表在u子树的所有节点,在u- ...

  3. (中等) HDU 5293 Tree chain problem,树链剖分+树形DP。

    Problem Description   Coco has a tree, whose vertices are conveniently labeled by 1,2,…,n.There are ...

  4. BZOJ4712洪水——动态DP+树链剖分+线段树

    题目描述 小A走到一个山脚下,准备给自己造一个小屋.这时候,小A的朋友(op,又叫管理员)打开了创造模式,然后飞到 山顶放了格水.于是小A面前出现了一个瀑布.作为平民的小A只好老实巴交地爬山堵水.那么 ...

  5. LOJ2269 [SDOI2017] 切树游戏 【FWT】【动态DP】【树链剖分】【线段树】

    题目分析: 好题.本来是一道好的非套路题,但是不凑巧的是当年有一位国家集训队员正好介绍了这个算法. 首先考虑静态的情况.这个的DP方程非常容易写出来. 接着可以注意到对于异或结果的计数可以看成一个FW ...

  6. Luogu P4643 【模板】动态dp(矩阵乘法,线段树,树链剖分)

    题面 给定一棵 \(n\) 个点的树,点带点权. 有 \(m\) 次操作,每次操作给定 \(x,y\) ,表示修改点 \(x\) 的权值为 \(y\) . 你需要在每次操作之后求出这棵树的最大权独立集 ...

  7. 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分

    树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...

  8. 【bzoj5210】最大连通子块和 树链剖分+线段树+可删除堆维护树形动态dp

    题目描述 给出一棵n个点.以1为根的有根树,点有点权.要求支持如下两种操作: M x y:将点x的点权改为y: Q x:求以x为根的子树的最大连通子块和. 其中,一棵子树的最大连通子块和指的是:该子树 ...

  9. 【bzoj4712】洪水 树链剖分+线段树维护树形动态dp

    题目描述 给出一棵树,点有点权.多次增加某个点的点权,并在某一棵子树中询问:选出若干个节点,使得每个叶子节点到根节点的路径上至少有一个节点被选择,求选出的点的点权和的最小值. 输入 输入文件第一行包含 ...

随机推荐

  1. 查询删除的SAP凭证

    标准报表查询:RSSCD100 函数模块:CHANGEDOCUMENT_DISPLAY, Display Change Documents 数据表查询:CDHDR, Change document h ...

  2. Redis高可用

    redis高可用只要在于三个方面 主从复制 哨兵机制 集群机制 主从复制 主从复制作用: 1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式.2.故障恢复:当主节点出现问题时,可 ...

  3. iPhone 横屏时默认会放大文字的问题

    有人说用 html { text-size-adjust: 100%; }我发现这个并不能解决问题.下面代码可以完美解决. 添加标签:<meta name="viewport" ...

  4. tcl之控制流-while

  5. MYSQL SQL高级查询技巧

    1.UNION,EXCEPT,INTERSECT运算符 A,UNION 运算符 UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表. ...

  6. MySQL基础 (麦子学员 php 第二阶段)

    通过my.ini配置文件修改字符集:客户端字符集设置:[mysql]default-character-set=utf8 [mysqld] character-set-server=utf8 .设置之 ...

  7. SpringCloud框架搭建+实际例子+讲解+系列五

    (4)服务消费者,面向前端或者用户的服务 本模块涉及到很多知识点:比如Swagger的应用,SpringCloud断路器的使用,服务API的检查.token的校验,feign消费者的使用.大致代码框架 ...

  8. select into from 和 insert into select

    select into from 和 insert into select都是用来复制表, 两者的主要区别为: select into from 要求目标表不存在,因为在插入时会自动创建. inser ...

  9. 裸机——I2C

    网上搜了些资料,碍于智商和基础,看不懂, 只有将S5PV210 数据手册关于I2C的部分,翻译记录下,留到以后用. 1.OVERVIEW The S5PV210 RISC microprocessor ...

  10. python-4函数式编程

    1-高阶函数 变量可以指向函数.   def add(x, y, f): 例如f参数为函数 编写高阶函数,就是让函数的参数能够接收别的函数. Python内建了map()和reduce()高阶函数. ...