http://www.lydsy.com/JudgeOnline/problem.php?id=4539

今天测试唯一会做的一道题。

按题目要求,如果暴力的把模板树往大树上仍,最后得到的大树是$O(n^2)$级别的,不能存储,更不能做了。

把模板树往大树上扔的过程我想象成了两个大节点进行连边,每个大节点代表模板树本身或一部分。

这相当于把初始的大树(此时和模板树相同)缩成一个大节点,每次把模板树的一部分缩成一个大节点往大节点构成的大树上连,最后连好的大节点构成的模板树是$O(n)$级别的。

每个节点里都套着一棵树,像树套树的模型。

这样在求距离和LCA的时候就可以先找到节点在哪个大节点里,求出在大节点内的一部分距离后,再在大节点构成的大树上倍增到LCA,统计距离。在LCA(大节点)中套着的树中继续倍增求LCA,统计距离。

每个大节点要维护的信息比较多;因为要按编号顺序从小到大加,所以还要在模板树的dfs序上用主席树查询第k大;倍增时还要注意特判几种特殊情况。在这里不再一一赘述。

测试时只有60分,后来拿到数据亲测一遍后发现到后期大树的节点到达$O(n^2)$级别,此时节点编号用int已经存不下了,所以存节点编号需要用long long。看来我还是too naive!!!

时间复杂度$O(n\log n)$

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100003;
int in() {
int k = 0, fh = 1; char c = getchar();
for(; c < '0' || c > '9'; c = getchar())
if (c == '-') fh = -1;
for(; c >= '0' && c <= '9'; c = getchar())
k = (k << 3) + (k << 1) + c - '0';
return k * fh;
} ll inll() {
ll k = 0; int fh = 1; char c = getchar();
for(; c < '0' || c > '9'; c = getchar())
if (c == '-') fh = -1;
for(; c >= '0' && c <= '9'; c = getchar())
k = (k << 3) + (k << 1) + c - '0';
return k * fh;
} int n, m, q; namespace Small {
struct node {
int nxt, to;
} E[N << 1];
int cnt = 0, point[N], f[N][18], deep[N], L[N], R[N], a[N];
bool vis[N];
void ins(int u, int v) {
E[++cnt].nxt = point[u]; E[cnt].to = v; point[u] = cnt;
} void dfs(int x) {
vis[x] = true; L[x] = ++cnt; a[cnt] = x;
for(int i = point[x]; i; i = E[i].nxt) if (!vis[E[i].to]) {
deep[E[i].to] = deep[x] + 1;
f[E[i].to][0] = x;
dfs(E[i].to);
}
R[x] = cnt;
} struct node2 {
int l, r, s;
} T[N * 20];
int root[N], tot = 0; void update(int &pos, int l, int r, int key) {
T[++tot] = T[pos]; pos = tot; ++T[pos].s;
if (l == r) return;
int mid = (l + r) >> 1;
if (key <= mid) update(T[pos].l, l, mid, key);
else update(T[pos].r, mid + 1, r, key);
}
void BuildPT() {
for(int i = 1; i <= n; ++i) {
root[i] = root[i - 1];
update(root[i], 1, n, a[i]);
}
} void work() {
int u, v;
for(int i = 1; i < n; ++i) {
u = in(); v = in();
ins(u, v); ins(v, u);
}
cnt = 0; memset(vis, 0, sizeof(vis));
deep[1] = 0; dfs(1);
for(int j = 1; j < 18; ++j)
for(int i = 1; i <= n; ++i) {
f[i][j] = f[f[i][j - 1]][j - 1];
}
BuildPT();
} int Sum(int x) {return R[x] - L[x] + 1;} int kth(int left, int right, int l, int r, int key) {
if (l == r) return l;
int mid = (l + r) >> 1, sum = T[T[right].l].s - T[T[left].l].s;
if (sum >= key) return kth(T[left].l, T[right].l, l, mid, key);
else return kth(T[left].r, T[right].r, mid + 1, r, key - sum);
}
int Query(int rt, int k) {
int l = L[rt], r = R[rt];
return kth(root[l - 1], root[r], 1, n, k);
} int todeep(int a, int b) {return deep[a] - deep[b];} int LCA(int u, int v) {
if (deep[u] < deep[v]) swap(u, v);
int d = deep[u] - deep[v];
for(int i = 17; i >= 0; --i)
if ((1 << i) & d)
u = f[u][i];
if (u == v) return d;
for(int i = 17; i >= 0; --i)
if (f[u][i] != f[v][i]) {
u = f[u][i]; v = f[v][i];
d += (1 << (i + 1));
}
return d + 2;
}
} namespace Big {
ll c[N][18];
int f[N][18], deep[N];
int tablenum = 0, table_to[N], table_rt[N];
ll table_l[N], table_r[N], up; int pos(ll x) {
int mid, left = 1, right = tablenum;
while (left < right) {
mid = (left + right) >> 1;
if (x < table_l[mid]) right = mid - 1;
else if (x > table_r[mid]) left = mid + 1;
else return mid;
}
return left;
} void work() {
int a; ll b;
up = n;
tablenum = 1; table_l[1] = 1; table_r[1] = n; table_rt[1] = 1;
for(int i = 1; i <= m; ++i) {
a = in(); b = inll();
int P = pos(b);
table_rt[++tablenum] = a;
table_l[tablenum] = up + 1;
up += Small::Sum(a);
table_r[tablenum] = up;
f[tablenum][0] = P;
deep[tablenum] = deep[P] + 1;
table_to[tablenum] = Small::Query(table_rt[P], b - table_l[P] + 1);
c[tablenum][0] = Small::todeep(table_to[tablenum], table_rt[P]) + 1;
}
for(int j = 1; j < 18; ++j)
for(int i = 1; i <= tablenum; ++i) {
f[i][j] = f[f[i][j - 1]][j - 1];
c[i][j] = c[i][j - 1] + c[f[i][j - 1]][j - 1];
} int changeu, changev, posu, vnum, posv, u, v;
ll U, V, ret;
for(int i = 1; i <= q; ++i) {
U = inll(); V = inll(); ret = 0;
posu = pos(U); posv = pos(V);
if (deep[posu] < deep[posv]) {
swap(posu, posv); swap(U, V);
} changeu = Small::Query(table_rt[posu], U - table_l[posu] + 1);
changev = Small::Query(table_rt[posv], V - table_l[posv] + 1); if (posu == posv) {
printf("%d\n", Small::LCA(changeu, changev));
continue;
} u = posu; v = posv;
for(int i = 17; i >= 0; --i)
if (deep[f[u][i]] > deep[v]) {
ret += c[u][i];
u = f[u][i];
} if (f[u][0] == v) {
ret += 1;
u = table_to[u];
v = changev;
ret += Small::LCA(u, v);
ret += Small::todeep(changeu, table_rt[posu]);
} else {
if (deep[u] > deep[v]) {
ret += c[u][0];
u = f[u][0];
}
for(int i = 17; i >= 0; --i)
if (f[u][i] != f[v][i]) {
ret += (c[u][i] + c[v][i]);
u = f[u][i];
v = f[v][i];
}
ret += 2;
ret += Small::LCA(table_to[u], table_to[v]);
ret += Small::todeep(changeu, table_rt[posu]) + Small::todeep(changev, table_rt[posv]);
} printf("%lld\n", ret);
}
}
} int main() {
n = in(); m = in(); q = in(); Small::work();
Big::work();
return 0;
}

遇到看到不可做的题一定要认真地思考,看透每个操作的本质,找到操作中重复的东西并利用它化简空间复杂度和时间复杂度。一定要想好了在写,考虑到方方面面,任何细节都很关键。就像这次测试因为节点编号没有用long long存储导致$O(n\log n)$复杂度的得分被卡成$O(n^2)$复杂度的得分。

【BZOJ 4539】【HNOI 2016】树的更多相关文章

  1. [HNOI 2016]树

    Description 题库链接 给你一棵 \(N\) 个节点根节点为 \(1\) 的有根树,结点的编号为 \(1\sim N\) :我们称这颗树为模板树.需要通过这棵模板树来构建一颗大树.构建过程如 ...

  2. bzoj 4540 [HNOI 2016] 序列 - 莫队算法 - Sparse-Table - 单调栈

    题目传送门 传送点I 传送点II 题目大意 给定一个长度为$n$的序列.询问区间$[l, r]$的所有不同的子序列的最小值的和. 这里的子序列是连续的.两个子序列不同当且仅当它们的左端点或右端点不同. ...

  3. [BZOJ 4537][Hnoi 2016]最小公倍数

    传送门 并查集+分块 看到题目可以想到暴力做法, 对于每个询问, 将所有a和b小于等于询问值的的边加入图中(用并查集), 如果询问的u和v在一个联通块中, 且该联通块的maxa和maxb均等与询问的a ...

  4. BZOJ 4551 HEOI 2016 树 (并查集)

    思路: 考虑时光倒流 这不就是并查集裸题了-----. //By SiriusRen #include <cstdio> #include <cstring> #include ...

  5. [BZOJ 4455] [ZJOI 2016] 小星星 (树形dp+容斥原理+状态压缩)

    [BZOJ 4455] [ZJOI 2016] 小星星 (树形dp+容斥原理+状态压缩) 题面 给出一棵树和一个图,点数均为n,问有多少种方法把树的节点标号,使得对于树上的任意两个节点u,v,若树上u ...

  6. 【BZOJ】3319: 黑白树

    http://www.lydsy.com/JudgeOnline/problem.php?id=3319 题意:给一棵n节点的树(n<=1e6),m个操作(m<=1e6),每次操作有两种: ...

  7. 【BZOJ】3319: 黑白树(并查集+特殊的技巧/-树链剖分+线段树)

    http://www.lydsy.com/JudgeOnline/problem.php?id=3319 以为是模板题就复习了下hld............................. 然后n ...

  8. BZOJ.4184.shallot(线段树分治 线性基)

    BZOJ 裸的线段树分治+线性基,就是跑的巨慢_(:з」∠)_ . 不知道他们都写的什么=-= //41652kb 11920ms #include <map> #include < ...

  9. [BZOJ 2759] 一个动态树好题

    [BZOJ 2759] 一个动态树好题 题目描述 首先这是个基环树. 然后根节点一定会连出去一条非树边.通过一个环就可以解除根的答案,然后其他节点的答案就可以由根解出来. 因为要修改\(p_i\),所 ...

  10. [LOJ 2083][UOJ 219][BZOJ 4650][NOI 2016]优秀的拆分

    [LOJ 2083][UOJ 219][BZOJ 4650][NOI 2016]优秀的拆分 题意 给定一个字符串 \(S\), 求有多少种将 \(S\) 的子串拆分为形如 AABB 的拆分方案 \(| ...

随机推荐

  1. Luogu题目集合[6/未完待续]

    1. P1327数列排序 题目描述 给定一个数列{an},这个数列满足ai≠aj(i≠j),现在要求你把这个数列从小到大排序,每次允许你交换其中任意一对数,请问最少需要几次交换? 输入输出格式 输入格 ...

  2. Vijos1680距离/openjudge2988计算字符串的距离[DP]

    描述 设有字符串X,我们称在X的头尾及中间插入任意多个空格后构成的新字符串为X的扩展串,如字符串X为”abcbcd”,则字符串“abcb_c_”,“_a_bcbcd_”和“abcb_c_”都是X的扩展 ...

  3. 利用注解进行sql反射代码示例

    @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String val ...

  4. 关于log4j的讨论

    1.LoggersLoggers组件在此系统中被分为五个级别:DEBUG.INFO.WARN.ERROR和FATAL.这五个级别是有顺序的,DEBUG < INFO < WARN < ...

  5. 更改项目名或者多个项目时,发现多个"Home"匹配的Controller时,解决方法

    [备份]异常信息:找到多个与名为“Home”的控制器匹配的类型.如果为此请求(“{controller}/{action}/{id}”)提供服务的 路由在搜索匹配此请求的控制器时没有指定命名空间,则会 ...

  6. 使用javascript实现的雪花飞舞的效果

    原作者是在body中不停的插入多个小div雪花来向下慢慢飘,一直飘到body的底部后,将雪花移除,于是,将原来的代码稍加修改,让他只是从屏幕的顶部飘落到屏幕底部(不是body的底部)后,就将雪花移除, ...

  7. Nginx+keepalived双机热备(主从模式)

    负载均衡技术对于一个网站尤其是大型网站的web服务器集群来说是至关重要的!做好负载均衡架构,可以实现故障转移和高可用环境,避免单点故障,保证网站健康持续运行.关于负载均衡介绍,可以参考:linux负载 ...

  8. PAT 1008. 数组元素循环右移问题 (20)

    一个数组A中存有N(N>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(M>=0)个位置,即将A中的数据由(A0 A1--AN-1)变换为(AN-M -- AN-1 A0 ...

  9. windows下安装memcache的基本步骤

    本文主要解决的是window下memcached的安装的问题,在使用的过程中经常会被第一步环境的配置搞混,本文结合我的配置过程和遇到的问题,做一个总结 1,开启php memcache的扩展,在文件  ...

  10. AlwaysOn Group Listener

    1.Listener是什么 Listener实际上是一个 VirtualNetworkName,客户端通过这个VNN来连接的具体的sqlserver实例 .Listener包含了DNS名称,port和 ...