题目传送门

题目大意

  给定一棵树,问有多少个无序三元组$(x, y, z)$使得这三个不同点在树上两两距离相等。

  考虑这三个点构成的虚树。选取一个"舒适"的计数对象。

  其中黄色的点是关键点,绿色的点是虚树上的虚点。

  在树形动态规划的时候通常考虑一个点子树内的情况会比较简单。因此考虑将计数对象设为虚树上最浅的一个点。

性质1 最近公共祖先深度较深的两点到它们的最近公共祖先的距离相等。

  证明 设这两点为$A, B$,第三点为$C$。当三个点的最近公共祖先相同时显然。否则第三点到这两点距离不相等。

  当三个点的最近公共祖先不相同时。设$A$和$B$两点的最近公共祖先为$D$。

  因为三个点公共祖先不同所以$C$在$D$的子树外。  

  此时虚树上有5个点,因为$A$和$C$、$B$和$C$的最近公共祖先是相同点,设它为$E$。

  那么有$dis(A, D) + dis(D, E) + dis(E, C) = dis(B, D) + dis(D, E) + dis(E, C)$

  所以$dis(A, D) = dis(B, D)$。

  用$f[i][j]$表示在$i$的子树中,到点$i$距离为$j$的点的个数。

  如果点$x, y$的LCA的深度为$d$,且它们到它们的LCA的距离相等,$g[i][j]$表示在$i$的子树中,有多少个点对$(x, y)$使得它们的LCA到点$i$的距离为$d - j$。

  为了避免算重,应当边在合并子树的信息的时候边计算答案。

  除了$f[i][0]$初始为1,其他初始为0。

  设$i$的某个子节点为$k$。那么转移有(顺序不是这样的):

  $f[i][j] = f[i][j] + f[k][j - 1]$

  $g[i][j] = g[i][j] + g[k][j + 1]$

  $g[i][j] = g[i][j] + f[i][j] * f[k][j - 1]$ (当前点是较深的LCA)

  $result = result + f[i][j] * g[k][j - 1] + g[i][j] * f[k][j + 1]$

  于是状态$O(n^{2})$,时间复杂度也是满满的$O(n^{2})$。成功爆炸。

  注意到前两个转移只是挪一挪指针,而初始的时候$f, g$几乎可以看成没有值。因此这一部分考虑直接通过指针赋值来实现$O(1)$转移。

  问题是,这个只能做一次。那就选择深度最深的子树进行转移。

性质2 该种做法时间复杂度$O(n)$。

  证明 对树进行长链剖分。当且仅当一条边是虚边的时候需要暴力进行转移,时间复杂度是这棵子树的深度。

  这个深度的意义可以看成虚边深的一端连接的长链的长度。因为长链一直连向叶子结点,中间不会有其他虚边。

  又因为长链覆盖整棵树。因此时间复杂度$O(n)$。

  然后开一个内存池动态分配空间。(其实直接new也是可以的,只是慢一点)。  

Code

 /**
* bzoj
* Problem#3522 & 4543
* Accepted & Accepted
* Time: 44ms & 768ms
* Memory: 19652k & 21900k
*/
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <vector>
#ifndef WIN32
#define Auto "%lld"
#else
#define Auto "%I64d"
#endif
using namespace std;
typedef bool boolean;
#define ll long long const int N = ; ll pool[ * N];
ll* top = pool; ll* alloc(int len) {
ll* rt = top;
top += len;
return rt;
} int n;
ll res = ;
vector<int> mg[N];
int ml[N], longs[N];
ll *f[N], *g[N]; inline void init() {
scanf("%d", &n);
for (int i = , u, v; i < n; i++) {
scanf("%d%d", &u, &v);
mg[u].push_back(v);
mg[v].push_back(u);
}
} void dfs1(int p, int fa) {
ml[p] = ;
int maxl = -, id = -;
for (int i = ; i < (signed) mg[p].size(); i++) {
int e = mg[p][i];
if (e == fa) continue;
dfs1(e, p);
ml[p] = max(ml[p], ml[e] + );
if (ml[e] > maxl)
maxl = ml[e], id = e;
}
longs[p] = id;
} void dfs2(int p, int fa, int& maxlen, int blank) {
maxlen = max(maxlen, ml[p]);
if (longs[p] != -) {
dfs2(longs[p], p, maxlen, blank + );
res += g[longs[p]][];
f[p] = f[longs[p]] - , f[p][] = ;
g[p] = g[longs[p]] + ;
} else {
f[p] = alloc(maxlen + + blank) + blank;
g[p] = alloc(maxlen + + blank);
f[p][] = ;
} for (int i = ; i < (signed) mg[p].size(); i++) {
int e = mg[p][i], mxlen = ;
if (e == fa || e == longs[p]) continue;
dfs2(e, p, mxlen, );
// assert(f[p] + ml[e] + 1 < org[p] + siz[p]);
for (int j = ; j < ml[e]; j++)
res += f[p][j] * g[e][j + ];
for (int j = ; j <= ml[e] + ; j++)
res += g[p][j] * f[e][j - ];
for (int j = ; j <= ml[e] + ; j++)
g[p][j] += f[p][j] * f[e][j - ];
for (int j = ; j <= ml[e]; j++)
f[p][j + ] += f[e][j];
for (int j = ; j <= ml[e]; j++)
g[p][j - ] += g[e][j];
}
} inline void solve() {
int mxlen = ;
dfs1(, );
dfs2(, , mxlen, );
printf(Auto, res);
} int main() {
init();
solve();
return ;
}

bzoj 3522 / 4543 [POI 2014] Hotel - 动态规划 - 长链剖分的更多相关文章

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

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

  2. BZOJ3522&4543 [POI2014]Hotel加强版 长链剖分

    上上周见fc爷用长链剖分秒题 于是偷偷学一学 3522的数据范围很小 可以暴力枚举每个点作为根节点来dp 复杂度$O(n^2)$ 考虑令$f[x][j]$表示以$x$为根的子树内距离$x$为$j$的点 ...

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

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

  4. 2018牛客网暑假ACM多校训练赛(第七场)I Tree Subset Diameter 动态规划 长链剖分 线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round7-I.html 题目传送门 -  https://www.n ...

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

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

  6. 【BZOJ4543】[POI2014]Hotel加强版 长链剖分+DP

    [BZOJ4543][POI2014]Hotel加强版 Description 同OJ3522数据范围:n<=100000 Sample Input 7 1 2 5 7 2 5 2 3 5 6 ...

  7. BZOJ 4543 2016北京集训测试赛(二)Problem B: thr 既 长链剖分学习笔记

    Solution 这题的解法很妙啊... 考虑这三个点可能的形态: 令它们的重心为距离到这三个点都相同的节点, 则其中两个点分别在重心的两棵子树中, 且到重心的距离相等; 第三个点可能在重心的一棵不同 ...

  8. 【BZOJ4543】Hotel加强版(长链剖分)

    [BZOJ4543]Hotel加强版(长链剖分) 题面 BZOJ,没有题面 洛谷,只是普通版本 题解 原来我们的\(O(n^2)\)做法是设\(f[i][j]\)表示以\(i\)为根的子树中,距离\( ...

  9. BZOJ 3653: 谈笑风生(离线, 长链剖分, 后缀和)

    题意 给你一颗有 \(n\) 个点并且以 \(1\) 为根的树.共有 \(q\) 次询问,每次询问两个参数 \(p, k\) .询问有多少对点 \((p, a, b)\) 满足 \(p,a,b\) 为 ...

随机推荐

  1. Linux基础(一)流程控制

    Shell 流程控制 if 语句语法格式1: 写成一行(适用于终端命令提示符):if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo ...

  2. (2)Python3笔记 数据类型之"组"(序列) 与 集合

    一. list [ 列表 ] ---- 任意数据类型的有序排列 1. list 操作类似 str操作 (参考上一节字符串操作) [1,2,3,4] [1,2,'a', True, [2,'b']]   ...

  3. LeetCode108.将有序数组转换为二叉搜索树

    将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定有序数组: [-10,-3,0, ...

  4. 解决乱码的方法是,在执行SQL语句之前,将MySQL以下三个系统参数设置为与服务器字符集character-set-server相同的字符集

    character-set-server/default-character-set:服务器字符集,默认情况下所采用的. character-set-database:数据库字符集. characte ...

  5. 浏览器页面请求js、css大文件处理

    当页面引用一个比较大的js和css文件时,会出现较大下载延迟,占用带宽的问题,如果一个应用里有很多这样的js或CSS文件,那么就需要优化了. 比如ext-all.js有1.4M,页面引用这个文件,正常 ...

  6. FileInputstream,FileOutputstream 和 byteArrayInputStream,byteArrayOutputStream

    你知道FileInputstream和FileOutputstream吗?FileInputstream,FileOutputstream分别是由抽象类Inputstream和Outputstream ...

  7. django之admin源码解析

    解析admin的源码 第一步:项目启动,加载settings文件中的 INSTALLED_APPS 里边有几个app就加载几个,按照注册顺序来执行. 第二步:其中加载的是admin.py,加载每一个a ...

  8. Firefox 功能笔记

    1.复制标签 说明:复制标签功能即新开一个与当前页一样的标签页,这个功能在Chrome中点击标签右键复制即可,但是在firefox中没有 Firefox中实现:Ctrl+拖动标签页

  9. java及spark2.X连接mongodb3.X单机或集群的方法(带认证及不带认证)

    首先,我们明确的是访问Mongos和访问单机Mongod并没有什么区别.接下来的方法都是既可以访问mongod又可以访问Mongos的. 另外,读作java写作scala,反正大家都看得懂...... ...

  10. golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息

    golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放 ...