【题解】HNOI2016树
大概最近写的这些题目都是仿生的代码……在这里先说明一下。可能比起做题记录来说更加像是学习笔记吧。之所以这样做主要还是因为感受到最近做的很多题目自己会做的都比较简单,不会做的又不敢触及,虽然也有所进步、学习了一些新的算法,但大体而言还是在原地踏步的样子。于是想要多观摩一下他人做题的过程并加以记录,也能开拓自己的视野,重新整理出新的思路,学习新的代码技巧。
这一道题目首先第一眼我们就可以放弃建出这棵树的想法:极端情况下树的节点可以达到n2的级别,1e10个节点光是建出来就已经不可接受,更别谈找lca求距离了。但我们注意到大树上的每一棵小树都是模板树的一部分,如果说只将每次复制的新树的根看做节点的话,本质不同的点最多也只有O(n)个。这里我们就形成了思路:将每一次复制出来的子树看做一个节点挂在大树上,形成一个树套树的结构。这样,我们可以利用倍增快速求出大树上节点与节点之间的距离,而位于小块(小模板树)内部的距离我们则单独处理。
其实感觉有点类似分块的呀,大块就直接跳,小块暴力 : ) (代码仿生)
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define int long long
int n, m, q; int read()
{
int x = , k = ;
char c;
c = getchar();
while(c < '' || c > '') { if(c == '-') k = -; c = getchar(); }
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x * k;
} namespace Small
{
int cnp = , cnt, tot, head[maxn], gra[maxn][], a[maxn];
int dep[maxn], root[maxn], L[maxn], R[maxn]; struct node
{
int last, to;
}E[maxn << ]; struct tree
{
int l, r, size;
}T[maxn * ]; void add(int u, int v)
{
E[cnp].to = v, E[cnp].last = head[u], head[u] = cnp ++;
} void dfs(int u)
{
L[u] = ++ cnt; a[cnt] = u;
for(int i = ; i < ; i ++)
gra[u][i] = gra[gra[u][i - ]][i - ];
for(int i = head[u]; i; i = E[i].last)
{
int v = E[i].to;
if(v == gra[u][]) continue;
dep[v] = dep[u] + ;
gra[v][] = u;
dfs(v);
}
R[u] = cnt;
} void update(int &now, int pre, int l, int r, int key)
{
now = ++ tot; T[now] = T[pre];
T[now].size ++;
if(l == r) return;
int mid = (l + r) >> ;
if(key <= mid) update(T[now].l, T[pre].l, l, mid, key);
else update(T[now].r, T[pre].r, mid + , r, key);
} void Build()
{
for(int i = ; i <= n; i ++)
update(root[i], root[i - ], , n, a[i]);
} void work()
{
for(int i = ; i < n; i ++)
{
int u = read(), v = read();
add(u, v), add(v, u);
}
dep[] = ; dfs();
Build();
} int Size(int x) { return R[x] - L[x] + ; } int query(int a, int b, int l, int r, int k)
{
if(l == r) return l;
int mid = (l + r) >> , size = T[T[b].l].size - T[T[a].l].size;
if(size >= k) return query(T[a].l, T[b].l, l, mid, k);
else return query(T[a].r, T[b].r, mid + , r, k - size);
} int ask(int rt, int k)
{
int l = L[rt], r = R[rt];
return query(root[l - ], root[r], , n, k);
} int to(int a, int b) { return dep[a] - dep[b]; } int lca(int u, int v)
{
if(dep[u] < dep[v]) swap(u, v);
for(int i = ; ~i; i --)
if(dep[gra[u][i]] >= dep[v]) u = gra[u][i];
for(int i = ; ~i; i --)
if(gra[u][i] != gra[v][i]) u = gra[u][i], v = gra[v][i];
return u == v ? u : gra[u][];
} int dis(int u, int v)
{
int LCA = lca(u, v);
return dep[u] + dep[v] - * dep[LCA];
}
} namespace Big
{
int up, tnum, tl[maxn], tr[maxn];
int dep[maxn], t_to[maxn], trt[maxn];
int c[maxn][], gra[maxn][]; int pos(int x)
{
int l = , r = tnum;
while(l < r)
{
int mid = (l + r) >> ;
if(x < tl[mid]) r = mid - ;
else if(x > tr[mid]) l = mid + ;
else return mid;
}
return l;
} void work()
{
up = n, tnum = , tl[] = , tr[] = n, trt[] = ;
for(int i = ; i <= m; i ++)
{
int a = read(), b = read();
int P = pos(b);
trt[++ tnum] = a; tl[tnum] = up + ;
up += Small :: Size(a); tr[tnum] = up;
gra[tnum][] = P; dep[tnum] = dep[P] + ;
t_to[tnum] = Small :: ask(trt[P], b - tl[P] + ); // 得到了b的编号
c[tnum][] = Small :: to(t_to[tnum], trt[P]) + ;
} for(int j = ; j < ; j ++)
for(int i = ; i <= tnum; i ++)
{
gra[i][j] = gra[gra[i][j - ]][j - ];
c[i][j] = c[i][j - ] + c[gra[i][j - ]][j - ];
} for(int i = ; i <= q; i ++)
{
int u = read(), v = read(), ret = ;
int posu = pos(u), posv = pos(v);
if(dep[posu] < dep[posv])
swap(posu, posv), swap(u, v);
int reu = Small :: ask(trt[posu], u - tl[posu] + );
int rev = Small :: ask(trt[posv], v - tl[posv] + ); if(posu == posv)
{
printf("%lld\n", Small :: dis(rev, reu));
continue;
} u = posu, v = posv;
for(int i = ; ~i; i --)
if(dep[gra[u][i]] > dep[v]) ret += c[u][i], u = gra[u][i]; if(gra[u][] == v)
{
ret += ;
u = t_to[u]; v = rev;
ret += Small :: dis(u, v);
ret += Small :: to(reu, trt[posu]);
printf("%lld\n", ret);
continue;
} if(dep[u] > dep[v]) ret += c[u][], u = gra[u][];
for(int i = ; ~i; i --)
if(gra[u][i] != gra[v][i])
{
ret += c[u][i], ret += c[v][i];
u = gra[u][i], v = gra[v][i];
}
ret += ;
ret += Small :: dis(t_to[u], t_to[v]);
ret += Small :: dis(reu, trt[posu]) + Small :: dis(rev, trt[posv]);
printf("%lld\n", ret);
}
}
} signed main()
{
n = read(), m = read(), q = read();
Small :: work();
Big :: work();
return ;
}
【题解】HNOI2016树的更多相关文章
- 【LG3248】[HNOI2016]树
[LG3248][HNOI2016]树 题面 洛谷 题解 因为每次你加入的点是原树上某一棵子树 那么我们一次加入一个点,代表一棵子树加到大树下面 那么我们要找到一个点在一个大点中用主席树在\(dfs\ ...
- BZOJ 4539: [Hnoi2016]树 [主席树 lca]
4539: [Hnoi2016]树 题意:不想写.复制模板树的子树,查询两点间距离. *** 终于有一道会做的题了...... 画一画发现可以把每次复制的子树看成一个大点来建一棵树,两点的lca一定在 ...
- 4539: [Hnoi2016]树
4539: [Hnoi2016]树 链接 分析: 主席树+倍增. 代码: #include<cstdio> #include<algorithm> #include<cs ...
- [BZOJ4539][HNOI2016]树(主席树)
4539: [Hnoi2016]树 Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 746 Solved: 292[Submit][Status][D ...
- POJ2182题解——线段树
POJ2182题解——线段树 2019-12-20 by juruoOIer 1.线段树简介(来源:百度百科) 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线 ...
- [HNOI2016]树(可持久化线段树+树上倍增)
[HNOI2016]树(可持久化线段树+树上倍增) 题面 给出一棵n个点的模板树和大树,根为1,初始的时候大树和模板树相同.接下来操作m次,每次从模板树里取出一棵子树,把它作为新树里节点y的儿子.操作 ...
- 题解-[HNOI2016]序列
题解-[HNOI2016]序列 [HNOI2016]序列 给定 \(n\) 和 \(m\) 以及序列 \(a\{n\}\).有 \(m\) 次询问,每次给定区间 \([l,r]\in[1,n]\),求 ...
- Qtree3题解(树链剖分(伪)+线段树+set)
外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意: 很明显吧.. 题解: 我的做法十分的暴力:树链剖分(伪)+线段树+\(set\)... ...
- Qtree3题解(树链剖分+线段树+set)
外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意 很易懂吧.. 题解 我的做法十分的暴力:树链剖分(伪)+线段树+ std :: set ...
随机推荐
- 在Python中使用正则表达式去掉字符串里的html标签
有时候会获得一些带html标签的字符串,需要把html标签去掉,获得干净的字符串,这时候可以使用正则表达式. 代码如下: import re htmeString = '''<ul id=&qu ...
- MySql指令的执行顺序
1:From 2:On 3:Join 4:Where 5:Group by 5.1:函数 6:Having 7:Select 8:Distinct 9:Order by
- webpack4提升180%编译速度
前言 对于现在的前端项目而言,编译发布几乎是必需操作,有的编译只需要几秒钟,快如闪电,有的却需要10分钟,甚至更多,慢如蜗牛.特别是线上热修复时,分秒必争,响应速度直接影响了用户体验,用户不会有耐心等 ...
- 3. 进程间通信IPC
一.概念 IPC: 1)在linux环境中的每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间是不能相互访问. 2)如果进程间要交换数据必须通过内核,在 ...
- python 排列组合
笛卡尔积(product): 假设集合A={a, b},集合B={0, 1, 2},则两个集合的笛卡尔积为{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2) ...
- 【EXCEL】SUMIFS(複数の条件を指定して数値を合計する)
分享:
- 在Android studio中用gradle打 jar 包(Mac下)
这两天公司要重构项目,以前的项目在eclipse上,准备迁移到Android studio上,需要对项目打包,于是我学习了Android studio中gradle打包的内容.我在公司用的Mac,在家 ...
- kill -9 vs killall
kill Linux中的kill命令用来终止指定的进程(terminate a process)的运行,是Linux下进程管理的常用命令.通常,终止一个前台进程可以使用Ctrl+C键,但是,对于一个后 ...
- linux手动安装flash插件
下载好之后,将解压的文件 1,将libflashplayer.so拷到firefox的插件目录/usr/lib/firefox/browser/plugin/ sudo cp libflashplay ...
- C#导出数据到CSV和EXCEL文件时数字文本被转义的解决方法
今天写C#导出datagrid数据到csv格式文件的时候,发现不管怎么尝试,凡是单元格里面全是数字的单元格,在用Excel打开的时候,都被自动转义成数据格式.数据查看极其不方便.最后google了一下 ...