题面

思路

首先,可以有一个$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. 基于asp.net MVC 的服务器和客户端的交互(一)

    架构思想 三层架构 提出了一种基于ASP.NET开发方式的三层架构的Web应用系统构造思想.其基本内容是:将面向对象的UML建模与Web应用系统开发 相结合,将整个系统分成适合ASP.NET开发方式的 ...

  2. Anaconda下安装 TensorFlow 和 keras 以及连接pycharm

    首先在官网下载Anaconda https://www.anaconda.com/download/ 安装时注意 勾选第一个,增加环境变量 安装好后再windows界面打开Anaconda Promp ...

  3. 第五章 标准I/O

    5.1 引言 本章说明标准 I/O 库.因为不仅在 UNIX 上,而且在很多操作系统上都实现了此库,所以它由 ISO C 标准说明. 标准 I/O 库处理很多细节,例如缓冲区分配,以优化长度执行 I/ ...

  4. B1081 检查密码 (15分)

    B1081 检查密码 (15分) 本题要求你帮助某网站的用户注册模块写一个密码合法性检查的小功能.该网站要求用户设置的密码必须由不少于6个字符组成,并且只能有英文字母.数字和小数点 .,还必须既有字母 ...

  5. MongoDb第一天

    安装之后进入cmd.进入到安装目录下的bin目录下. 任意选一个空目录,建立db,log的文件夹.之后终端命令行里面输入回车. D:\ProgramFiles\MongoDB\Server\3.6\b ...

  6. EVALUation mode running with code size limit:2k keil进行仿真过程中出现的报错

    EVALUation mode running with code size limit:2k 如果keil软件未破解,会限制程序的存储大小.第一是你的软件没有破解,不能编译2K以上的程序:这种情况下 ...

  7. Android log 引发的血案

    今天调试代码,我打印了一个东西: Log.d("WelcomeActivity", res.str); 结果总是代码执行不到这一行的下一行,程序也没有挂掉.后来,我自己去想各种可能 ...

  8. android 布局preview 技巧

    最近开始看老外写的文章,博客,嗯,不能说比国人写的好,但是感觉看着很爽.真的,一手资料就是爽. 嗯,自己做得不错,第一次看外文博客,我知道自己怎么看的,一句话一句话看下来的,越看越有感觉. 下面这个 ...

  9. 2.route路由配置

    转自 http://www.cnblogs.com/peida/archive/2013/03/05/2943698.html Linux系统的route命令用于显示和操作IP路由表(show / m ...

  10. laravel5.5门面

    Facades为应用程序的 服务容器 中可用的类提供了一个 静态接口 . 最直观的好处 就是需记住必须手动注入或配置的长长的类名.因此有人也理解Facades就是一个"快捷别名" ...