[OJ#63]树句节够提
Posted xjr_01
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[OJ#63]树句节够提相关的知识,希望对你有一定的参考价值。
[OJ#63]树句节够提
试题描述
给定一棵节点数为 N 的有根树,其中 1 号点是根节点,除此之外第 i 个节点的父亲为 fi。每个节点有一个权值 Ai,所有边权均为 1。
给定 Q 个询问,每个询问以一个二元组 (x,k) 的形式给出,表示询问以 x 为根的子树内,与 x 距离至少为 k 的所有节点权值之和。
由于输出量可能过大,我们使用以下方式减少输出量。
void print(int q, long long* ans, int lim) {
for(int i = 1; i <= q; ) {
long long res = 0;
for(int j = i; j <= min(q, i + lim - 1); j++) res ^= ans[j];
i += lim;
printf("%lld\n", res);
}
}
程序中 ansi 表示第 i 次询问的答案,你需要在你的程序末尾调用以上函数来输出答案。
输入
第一行为一个正整数 N。
第二行为 N 个正整数 Ai。
第三行为 N?1 个正整数 fi,第 i 个正整数表示 i+1 的父亲节点。
第四行为一个正整数 Q。
接下来 Q 行每行两个整数 x、k。
最后一行为一个正整数 lim。
输出
在你的程序末尾调用以上函数来输出答案。
输入示例
7 1 2 3 4 5 6 7 1 1 2 2 3 3 5 2 0 2 1 6 1 6 0 1 1 1
输出示例
11 9 0 6 27
数据规模及约定
对于 20% 的数据,1≤N,Q≤2501
对于 70% 的数据,1≤N,Q≤252501
对于 100% 的数据,1≤N,Q≤2525010,1≤Ai≤52501,1≤fi≤i?1,1≤x,k+1,lim≤N
保证单个输出文件大小不超过 0.5MB,但如果你需要使用Hack功能,请保证Max(1,floor(N/10000))≤lim。
题解
注意:此题标算 O(n)。
长链剖分“新”用法?(就好像之前写过长链剖分的题一样。。。)
先离线,将所有询问放入树上对应节点,然后给树进行长链剖分(其实长链剖分主要目的是将“长链”放到区间的连续一段)。然后在处理询问时,因为对于一个子树 x,深度一样的节点地位一样,所以可以将子树的信息全都“压缩”到长链上,由于长链是这个子树 x 中一端在 x 上的最长链,所以能够保证所有的节点都有地方存。
由于一条长链对应一段连续区间,在询问某个深度的时候就可以 O(1) 用数组查询了。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <algorithm> using namespace std; int read() { int x = 0, f = 1; char c = getchar(); while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); } return x * f; } #define maxn 2525020 #define LL long long int n, q, fa[maxn], m, head[maxn], nxt[maxn], to[maxn], A[maxn]; void AddEdge(int a, int b) { to[++m] = b; nxt[m] = head[a]; head[a] = m; return ; } int dep[maxn], mxd[maxn], son[maxn], pos[maxn], clo; void build(int u) { mxd[u] = dep[u]; for(int e = head[u]; e; e = nxt[e]) { dep[to[e]] = dep[u] + 1; build(to[e]); mxd[u] = max(mxd[u], mxd[to[e]]); if(!son[u] || mxd[to[e]] > mxd[son[u]]) son[u] = to[e]; } return ; } void gett(int u) { pos[u] = ++clo; if(son[u]) gett(son[u]); for(int e = head[u]; e; e = nxt[e]) if(to[e] != son[u]) gett(to[e]); return ; } struct Que { int head[maxn], nxt[maxn], dep[maxn]; Que() { memset(head, 0, sizeof(head)); } void Insert(int u, int d, int id) { dep[id] = d; nxt[id] = head[u]; head[u] = id; return ; } } que; LL sum[maxn], Ans[maxn]; void solve(int u) { if(son[u]) solve(son[u]); for(int e = head[u]; e; e = nxt[e]) if(to[e] != son[u]) { solve(to[e]); for(int i = 0; i <= mxd[to[e]] - dep[to[e]]; i++) sum[pos[u]+1+i] += sum[pos[to[e]]+i]; } sum[pos[u]] = (son[u] ? sum[pos[son[u]]] : 0) + A[u]; for(int e = que.head[u]; e; e = que.nxt[e]) Ans[e] = (que.dep[e] <= mxd[u] - dep[u]) ? sum[pos[u]+que.dep[e]] : 0; return ; } void print(int q, int lim) { for(int i = 1; i <= q; ) { long long res = 0; for(int j = i; j <= min(q, i + lim - 1); j++) res ^= Ans[j]; i += lim; printf("%lld\n", res); } return ; } int main() { n = read(); for(int i = 1; i <= n; i++) A[i] = read(); for(int i = 2; i <= n; i++) { fa[i] = read(); AddEdge(fa[i], i); } build(1); gett(1); q = read(); for(int i = 1; i <= q; i++) { int x = read(), d = read(); que.Insert(x, d, i); } solve(1); print(q, read()); return 0; }
然而这题我写的 O(nlogn) 的算法比上面的 O(n) 要快 233333
注意不能直接强上主席树,因为空间不够。
考虑到每个询问就是问子树内深度大于等于某个值(k+dep[x],k 表示该询问的参数,dep[x] 表示该询问中节点 x 的深度,不妨称每个询问的 k+dep[x] 为它的绝对深度)的所有的点权和,所以我们将询问按它们的绝对深度从大到小排序,然后依次处理。那么问题变成每次添加一个点,然后询问区间和,可以用树状数组很快地实现。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <algorithm> using namespace std; const int BufferSize = 1 << 16; char buffer[BufferSize], *Head, *Tail; inline char Getchar() { if(Head == Tail) { int l = fread(buffer, 1, BufferSize, stdin); Tail = (Head = buffer) + l; } return *Head++; } int read() { int x = 0, f = 1; char c = Getchar(); while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = Getchar(); } while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = Getchar(); } return x * f; } #define maxn 2525020 #define LL long long int n, q, fa[maxn], m, head[maxn], nxt[maxn], to[maxn], A[maxn]; void AddEdge(int a, int b) { to[++m] = b; nxt[m] = head[a]; head[a] = m; return ; } struct Level { int head[maxn], nxt[maxn]; Level() { memset(head, 0, sizeof(head)); } void Insert(int u, int level) { nxt[u] = head[level]; head[level] = u; return ; } } lev; int dep[maxn], dl[maxn], dr[maxn], clo; void build(int u) { dl[u] = ++clo; lev.Insert(u, dep[u]); for(int e = head[u]; e; e = nxt[e]) { dep[to[e]] = dep[u] + 1; build(to[e]); } dr[u] = clo; return ; } struct Que { int u, d, id; Que() {} Que(int _1, int _2, int _3): u(_1), d(_2 + dep[u]), id(_3) {} bool operator < (const Que& t) const { return d < t.d; } } qs[maxn]; LL C[maxn]; void add(int x, int v) { for(; x <= n; x += x & -x) C[x] += v; return ; } LL que(int x) { LL sum = 0; for(; x; x -= x & -x) sum += C[x]; return sum; } LL Ans[maxn]; int num[100], cntn; void putnum(LL x) { cntn = 0; while(x) num[cntn++] = x % 10, x /= 10; for(int i = cntn - 1; i >= 0; i--) putchar(num[i] + ‘0‘); if(!cntn) putchar(‘0‘); putchar(‘\n‘); return ; } void print(int q, int lim) { for(int i = 1; i <= q; ) { long long res = 0; for(int j = i; j <= min(q, i + lim - 1); j++) res ^= Ans[j]; i += lim; putnum(res); } return ; } int main() { n = read(); for(int i = 1; i <= n; i++) A[i] = read(); for(int i = 2; i <= n; i++) { fa[i] = read(); AddEdge(fa[i], i); } build(1); q = read(); for(int i = 1; i <= q; i++) { int x = read(), k = read(); qs[i] = Que(x, k, i); } sort(qs + 1, qs + q + 1); for(int i = q, j = n; i; i--) { while(j >= qs[i].d) { for(int e = lev.head[j]; e; e = lev.nxt[e]) add(dl[e], A[e]); j--; } Ans[qs[i].id] = que(dr[qs[i].u]) - que(dl[qs[i].u] - 1); } print(q, read()); return 0; }
此题略丧病。。。
以上是关于[OJ#63]树句节够提的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode OJ 63. Unique Paths II
[LeedCode OJ]#63 Unique Paths II