[luogu5002]专心OI - 找祖先
【传送门】
我们还是先将一下算法的步骤,待会再解释起来方便一点。
算法步骤
首先我们算出每个子树的\(size\)。
我们就设当前访问的节点
然后我们就得到了当前这个节点的答案是这个树整个的\(size\)的两倍\(-1\),再加上两两子树的\(size\)的积。
也就是$ans=size[u] \times 2 - 1 +\sum size[i] * size[j] $
其中\(i\)和\(j\)是u的儿子。
一下就是核心代码:
register LL ans = sz[p] * 2 - 1;
for (Ri i = head[p] ; i ; i = edge[i].next)
{
if ( edge[i].to == fa[p]) continue;
sz_rest = sz[p] - sz[edge[i].to] - 1 ;
ans = (ans + (sz_rest * sz[edge[i].to] ) ) % Mod;
}
解释一下
首先,我们要明白,如果要以当前这个节点为\(LCA\)的话,那么两个节点必须不能是是这个当前这个要查询的节点的父亲,这个应该没有问题。
那么这两个节点必须是在当前查询节点上,或者是在这个节点的不同子树中。(注:这个一定是要在不同的子树中,不然\(LCA\)就会是在这个子树当中,而不是当前的节点)
那么我们要考虑两种情况
- 如果其中一个点是我们要访问的节点,那么另外一个点就可以是这个这棵子树中任意的点,但是要注意两个细节,这些点对是有顺序的,所以答案需要\(\times 2\),第二个细节是我们要注意根节点是只算一次的,因为相同的点放前放后都是一样的,只算一种。
- 如果两个点是在不同的子树中,那么一棵子树中每一个节点都一定可以和另外一课子树中的任意一点相组成一个合法的点对,那么就根据乘法原理,将这些子树中的节点数相乘在相加就是我们得到的答案了。
那么我们就得到了答案呀,答案就是上文中所提到的$ans=size[u] \times 2 - 1 +\sum size[i] * size[j] $
以下是代码,但是是错的,会TLE一个点。
# include <bits/stdc++.h>
# define Ri register int
# define for1(i,a,b) for (Ri i(a) ; i <= b; i++ )
# define for2(i,a,b) for (Ri i(a) ; i >= b; i-- )
using namespace std;
typedef long long LL;
const int Mod = 1e9 + 7;
const int Maxn = 50005;
struct Edge {
int to , next ;
} edge[Maxn<<1] ;
int head[Maxn<<1];
int sz[Maxn], fa[Maxn];
int root, n ,m , Nedge;
inline int read ()
{
int w = 0,x = 0; char ch = 0;
while ( !isdigit(ch) ) { w |= ch == '-'; ch = getchar() ; }
while ( isdigit(ch) ) { x = (x<<1) + (x<<3) + (ch^48); ch = getchar() ; }
return w ? -x : x ;
}
inline void Add_Edge (int u, int v)
{
edge[Nedge] = (Edge) {v , head[u]};
head[u] = Nedge++;
}
inline void dfs (int u , int fat)
{
fa[u] = fat;
sz[u] = 1;
for (int i = head[u] ; i != -1; i = edge[i].next )
{
int v = edge[i].to;
if ( v == fa[u] ) continue;
dfs (v ,u );
sz[u] += sz[v];
}
}
int main ()
{
memset( head, -1 ,sizeof(head) );
n = read(), root = read(), m = read();
for1 (i , 1 , n-1)
{
int u = read(),v = read();
Add_Edge(u, v); Add_Edge(v, u);
}
dfs (root , -1);
for1 (T ,1 , m )
{
int p = read();
LL ans = sz[p] * 2 - 1;
for (int i = head[p] ; i != -1 ; i = edge[i].next)
{
for (int j = head[p] ; j != -1 ; j = edge[j].next)
{
if (i == j || edge[i].to == fa[p] || edge[j].to == fa[p]) continue;
ans = (ans + sz[edge[i].to] * sz[edge[j].to]) % Mod;
}
}
printf ("%lld\n", ans);
}
return 0 ;
}
用尽了卡常技巧,都没法A掉最后一个点,为什么?
因为最后一个点太大了,如果按照我们的做法,是\(O(n^3+n^2+ m + n)\)的复杂度,这样实在是太不优美了。
我们来想一想优化。
其实我们并不需要枚举两个儿子,因为我们还有一个父亲没有用到,因为任意一个儿子需要\(\times\)不属于这个子树的节点的数量,那么这个数量就是父亲的\(size-\)儿子\(size-1\)。\(-1\)是减去父亲的节点
那么也就是优化成\(O(2\times n^2 + n + m)\)
其实也就是优化了主程序的一小部分。
for1 (T ,1 , m )
{
register int p = read() ;
register int sz_rest;
register LL ans = sz[p] * 2 - 1;
for (Ri i = head[p] ; i != -1 ; i = edge[i].next)
{
if ( edge[i].to == fa[p]) continue;
sz_rest = sz[p] - sz[edge[i].to] - 1 ;
ans = (ans + (sz_rest * sz[edge[i].to] ) ) % Mod;
}
printf ("%lld\n", ans);
}
但是还是做不了,还是会T掉一个点,那么我们就需要更加牛逼的优化了,因为答案不大,我们就用一个桶记录这个答案,这样我们就把超大的\(m\)缩小成了\(n\)了,这样我就刚好过掉了,而且速度老快惹QAQ!
以下是正解代码
# include <cstdio>
# include <cstring>
# include <ctype.h>
# define Ri register int
# define for1(i,a,b) for (Ri i(a) ; i <= b; i++ )
# define for2(i,a,b) for (Ri i(a) ; i >= b; i-- )
using namespace std;
typedef long long LL;
const int Mod = 1e9 + 7;
const int Maxn = 50005;
struct Edge {
int to , next ;
} edge[Maxn<<1] ;
int head[Maxn<<1];
int sz[Maxn], fa[Maxn];
int root, n ,m , Nedge;
LL Ans[Maxn];
inline int read ()
{
int w = 0,x = 0; char ch = 0;
while ( !isdigit(ch) ) { w |= ch == '-'; ch = getchar() ; }
while ( isdigit(ch) ) { x = (x<<1) + (x<<3) + (ch^48); ch = getchar() ; }
return w ? -x : x ;
}
inline void Add_Edge (int u, int v)
{
edge[Nedge] = (Edge) {v,head[u]};
head[u] = Nedge++;
}
inline void dfs (int u , int fat) //计算size,并标记父亲
{
fa[u] = fat; sz[u] = 1;
for (Ri i = head[u] ; i ; i = edge[i].next )
{
int v = edge[i].to;
if ( v == fa[u] ) continue;
dfs (v ,u );
sz[u] += sz[v];//累加size
}
}
int main ()
{
n = read(), root = read(), m = read(); Nedge=1;
for1 (i , 1 , n-1)
{
int u = read(), v = read();
Add_Edge(u, v); Add_Edge(v, u);
}
dfs (root , -1);
for1 (T ,1 , m )
{
register int p = read() ;
if ( Ans[p] != 0 ) {printf("%lld\n", Ans[p] ); continue; } //如果当前的答案已经算过了,直接输出
register int sz_rest; //表示剩下的
register LL ans = sz[p] * 2 - 1; //算第一部分的答案
for (Ri i = head[p] ; i ; i = edge[i].next)
{
if ( edge[i].to == fa[p]) continue;
sz_rest = sz[p] - sz[edge[i].to] - 1 ;//算第二部分的答案。
ans = (ans + (sz_rest * sz[edge[i].to] ) ) % Mod;//累加答案
}
Ans[p] = ans ;
printf ("%lld\n", ans);
}
return 0 ;
}
[luogu5002]专心OI - 找祖先的更多相关文章
- P5002 专心OI - 找祖先
P5002 专心OI - 找祖先 给定一棵有根树(\(n \leq 10000\)),\(M \leq 50000\) 次询问, 求以 \(x\) 为 \(LCA\) 的点对个数 错误日志: 看下面 ...
- 【洛谷 5002】专心OI - 找祖先 (树上计数)
专心OI - 找祖先 题目背景 \(Imakf\)是一个小蒟蒻,他最近刚学了\(LCA\),他在手机\(APP\)里看到一个游戏也叫做\(LCA\)就下载了下来. 题目描述 这个游戏会给出你一棵树,这 ...
- 洛谷P5002 专心OI - 找祖先
题目概括 题目描述 这个游戏会给出你一棵树,这棵树有\(N\)个节点,根结点是\(R\),系统会选中\(M\)个点\(P_1,P_2...P_M\). 要Imakf回答有多少组点对\((u_i,v_i ...
- luogu P5002 专心OI - 找祖先
题目描述 这个游戏会给出你一棵树,这棵树有NN个节点,根结点是RR,系统会选中MM个点P_1,P_2...P_MP 1 ,P 2 ...P M ,要Imakf回答有多少组点对(u_i,v_ ...
- 洛谷【P5004 专心OI - 跳房子】 题解
题目链接 https://www.luogu.org/problem/P5004 洛谷 P5004 专心OI - 跳房子 Imakf有一天参加了PINO 2017 PJ组,他突然看见最后一道题 他十分 ...
- HDOJ 题目2475 Box(link cut tree去点找祖先)
Box Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submi ...
- [luogu5004]专心OI - 跳房子【矩阵加速+动态规划】
传送门:https://www.luogu.org/problemnew/show/P5004 分析 动态规划转移方程是这样的\(f[i]=\sum^{i-m-1}_{j=0}f[j]\). 那么很明 ...
- 【LuoguP5004】 专心OI - 跳房子
首先这是一道计数类DP,那我们得先推式子,经过瞎掰乱凑,经过认真分析,我们可以得到这样的方程 F(N)=F(0)+F(1)+....+F(N-M-1) 所有F初值为1,F(1)=2 ANS=F(N+M ...
- 「P5004」专心OI - 跳房子 解题报告
题面 把\(N\)个无色格子排成一行,选若干个格子染成黑色,要求每个黑色格子之间至少间隔\(M\)个格子,求方案数 思路: 矩阵加速 根据题面,这一题似乎可以用递推 设第\(i\)个格子的编号为\(i ...
随机推荐
- redis 配置 架构 基础
redis 官网 redis.io io为某国家域名后缀 有redis各种版本. java 版本 又分各种工具 clients 下 RedisClient为图形化管理界面 Jedis 才是jav ...
- 第一章:模型层model layer -- Django从入门到精通系列教程
该系列教程系个人原创,并完整发布在个人官网刘江的博客和教程 所有转载本文者,需在顶部显著位置注明原作者及www.liujiangblog.com官网地址. 题外话: Django的教程写到这里,就进入 ...
- [UWP 自定义控件]了解模板化控件(8):ItemsControl
1. 模仿ItemsControl 顾名思义,ItemsControl是展示一组数据的控件,它是UWP UI系统中最重要的控件之一,和展示单一数据的ContentControl构成了UWP UI的绝大 ...
- CentOS 6下gcc升级的操作记录(由默认的4.4.7升级到6.4.0版本)
机房一台centos6.9机器部署了jenkins发布系统,开发人员在用node编译js,发现依赖的gcc版本低了,故需要将gcc升级到高版本(至少5.0版本以上),这里选择升级到6.4.0版本,下面 ...
- LInux下设置账号有效时间 以及 修改用户名(同时修改用户组名和家目录)
在linux系统中,默认创建的用户的有效期限都是永久的,但有时候,我们需要对某些用户的有效期限做个限定!比如:公司给客户开的ftp账号,用于客户下载新闻稿件的.这个账号是有时间限制的,因为是付费的.合 ...
- StackOverflow 问题
StackOverflow 这个问题一般是你的程序里头可能是有死循环或递归调用所产生的:可以查看一下你的程序,也可以增大你JVM的内存~~~在Eclipse中JDK的配置中加上 -XX:MaxD ...
- 置换群 Burnside引理 Pólya定理(Polya)
置换群 设\(N\)表示组合方案集合.如用两种颜色染四个格子,则\(N=\{\{0,0,0,0\},\{0,0,0,1\},\{0,0,1,0\},...,\{1,1,1,1\}\}\),\(|N|= ...
- easyUI中textbox或number的数值大小校验
例:textbox里面,要求做两个textbox名字为(A,B),其中两个的数字大小范围是-10~10之间,之后其中A的值必须大于B所填的数字,如果输入错误,则提示出弹出框,并清空数据. <!D ...
- 【课程总结】Linux内核分析课程总结
程涵 原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 每周实验报告: 反汇编一个简单的C程序 ...
- 20172319 《Java程序设计教程》第8周学习总结
20172319 2018.04.24-05.03 <Java程序设计教程>第8周学习总结 目录 教材学习内容总结 教材学习中的问题和解决过程 代码调试中的问题和解决过程 代码托管 上周考 ...