题目链接

Description

"奋战三星期,造台计算机"。小G响应号召,花了三小时造了台普通计算姬。普通计算姬比普通计算机要厉害一些。普通计算机能计算数列区间和,而普通计算姬能计算树中子树和。更具体地,小G的计算姬可以解决这么个问题:

给定一棵\(n\)个节点的带权树,节点编号为\(1\)到\(n\),以\(root\)为根,设\(val[p]\)表示以点\(p\)为根的这棵子树中所有节点的权值和。计算姬支持下列两种操作:

  1. 给定两个整数\(u\),\(v\),修改点\(u\)的权值为\(v\)。

  2. 给定两个整数\(l,r\),计算\(val[l]+val[l+1]+\cdots+val[r-1]+val[r]\)

尽管计算姬可以很快完成这个问题,可是小G并不知道它的答案是否正确,你能帮助他吗?

注:原题面中标识符为\(sum\),为以下表述清楚,在此擅改为\(val\).

题解

参考

https://www.cnblogs.com/ljh2000-jump/p/6514792.html

http://blog.csdn.net/qq_33229466/article/details/70837585

1 - dfs序

首先,\(val\)的计算依赖于\(dfs\)

\(val[p]\)即为\(dfs\)序上的\(l[p]\)到\(r[p]\)这段区间的节点权值和。

2 - 分块

如何处理\(val\)的和?

给定的\([l,r]\)区间在树上的分布毫无规律可言,因此只能分块

通过分块维护每一块的和\(sum\),不完整的块暴力算,完整的块直接统计和。

3 - 修改造成的影响

修改单点权值子树和会造成什么影响?

修改点\(u\)的权值,会改变其所有祖先的子树和。

故,预处理出一个信息:用\(f[u][i]\)表示在第\(i\)块中有多少个点是\(u\)的祖先。

有了这个信息,修改点\(u\)的权值对第\(i\)块的影响就是:sum[i] += f[u][i]*delta;

这个信息怎么获得呢?

\(dfs\)时,进一个点\(u\)时给其所在块的计数\(+1\),出的时候再\(-1\).

每次\(dfs\)到一个点,遍历所有的块,\(f[u][i]\)即为此时计数器的值。

时间复杂度:\(O(n\sqrt n)\)

4 - 不完整的块

上面说到,不完整的块暴力算,即对于块内的每个点\(i\),直接计算\(val[i]\),也即\(dfs\)序上的\(l[i]\)到\(r[i]\)这段区间的节点权值和。

于是,我们现在的问题就是:单点修改,区间查询

法一:树状数组

维护每个节点的权值。

对于每个操作:

修改:\(O(logn)\)

查询:对不完整的块中的每个点都进行一次查询:\(O(\sqrt n*logn)\)

法二:分块

维护前缀和序列。

修改点\(i\)的权值,即相当于修改区间\([i,n]\)的值。

用\(tag\)表示每一块的整体的增量。

对于每个操作:

修改:\(O(\sqrt n)\)

查询:对不完整的块中的每个点都进行一次查询:\(O(\sqrt n*1)=O(\sqrt n)\)

Code

以下两个版本其实大同小异,差别就在\(add\)和\(query\)的不同实现...。

Ver 1

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 100010
#define maxm 320
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
struct node { int to, ne; }edge[maxn<<1];
int ne[maxn], l[maxn], r[maxn], t[maxn], f[maxn][maxm], bl[maxn], v[maxn], vv[maxn];
int tot, nb, n, blo, cnt, m;
LL c[maxn], val[maxn], tag[maxm], pre[maxn];
ULL sum[maxm];
void addEdge(int u, int v) {
edge[tot] = {v, ne[u]};
ne[u] = tot++;
}
void dfs(int u, int fa) {
++t[bl[u]];
F2(i, 1, nb) f[u][i] = t[i];
l[u] = ++cnt;
for (int i = ne[u]; ~i; i = edge[i].ne) {
int v = edge[i].to;
if (v!=fa) dfs(v,u);
}
r[u] = cnt;
--t[bl[u]];
}
inline int lowbit(int x) { return x & (-x); }
inline void add(int x, LL v) { while (x<=n) c[x]+=v, x+=lowbit(x); }
inline LL query(int x) { LL ret=0; while (x) ret+=c[x], x-=lowbit(x); return ret; }
void init() {
F2(i, 1, n) val[i] = query(r[i]) - query(l[i]-1);
F2(i, 1, nb) {
F2(j, (i-1)*blo+1, min(i*blo, n)) sum[i] += val[j];
}
}
void modify(int u, int x) {
LL delta=x-v[u];
add(l[u], delta);
F2(i, 1, nb) sum[i] += f[u][i]*delta;
v[u] = x;
}
ULL ask(int ll, int rr) {
ULL ret=0;
F2(i, ll, min(bl[ll]*blo, rr)) ret += query(r[i])-query(l[i]-1);
if (bl[ll]!=bl[rr]) {
F2(i, (bl[rr]-1)*blo+1, rr) ret += query(r[i])-query(l[i]-1);
}
F(i, bl[ll]+1, bl[rr]) ret += sum[i];
return ret;
}
int main() {
scanf("%d%d", &n, &m); blo = sqrt(n);
F2(i, 1, n) {
scanf("%d", &v[i]);
bl[i] = (i-1)/blo+1;
}
nb = bl[n];
int rt;
memset(ne, -1, sizeof ne);
F(i, 0, n) {
int u, v;
scanf("%d%d", &u, &v);
if (!u) rt = v;
else addEdge(u, v), addEdge(v, u);
}
dfs(rt, -1);
F2(i, 1, n) add(l[i], v[i]);
init();
F(i, 0, m) {
int op, l, r;
scanf("%d%d%d", &op,&l,&r);
if (op==1) modify(l, r);
else printf("%llu\n", ask(l, r));
}
return 0;
}

Ver 2

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 100010
#define maxm 320
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
struct node { int to, ne; }edge[maxn<<1];
int ne[maxn], l[maxn], r[maxn], t[maxn], f[maxn][maxm], bl[maxn], v[maxn], vv[maxn];
int tot, nb, n, blo, cnt, m;
LL val[maxn], tag[maxm], pre[maxn];
ULL sum[maxm];
void addEdge(int u, int v) {
edge[tot] = {v, ne[u]};
ne[u] = tot++;
}
void dfs(int u, int fa) {
++t[bl[u]];
F2(i, 1, nb) f[u][i] = t[i];
l[u] = ++cnt;
for (int i = ne[u]; ~i; i = edge[i].ne) {
int v = edge[i].to;
if (v!=fa) dfs(v,u);
}
r[u] = cnt;
--t[bl[u]];
}
inline void add(int l, int r, LL v) {
F2(i, l, min(r, bl[l]*blo)) pre[i] += v;
if (bl[l]!=bl[r]) {
F2(i, (bl[r]-1)*blo+1, r) pre[i] += v;
}
F(i, bl[l]+1, bl[r]) tag[i] += v;
}
inline LL query(int x) { return pre[x]+tag[bl[x]]; }
void init() {
F2(i, 1, n) val[i] = query(r[i]) - query(l[i]-1);
F2(i, 1, nb) {
F2(j, (i-1)*blo+1, min(i*blo, n)) sum[i] += val[j];
}
}
void modify(int u, int x) {
LL delta=x-v[u];
add(l[u], n, delta);
F2(i, 1, nb) sum[i] += f[u][i]*delta;
v[u] = x;
}
ULL ask(int ll, int rr) {
ULL ret=0;
F2(i, ll, min(bl[ll]*blo, rr)) ret += query(r[i])-query(l[i]-1);
if (bl[ll]!=bl[rr]) {
F2(i, (bl[rr]-1)*blo+1, rr) ret += query(r[i])-query(l[i]-1);
}
F(i, bl[ll]+1, bl[rr]) ret += sum[i];
return ret;
}
int main() {
scanf("%d%d", &n, &m); blo = sqrt(n);
F2(i, 1, n) {
scanf("%d", &v[i]);
bl[i] = (i-1)/blo+1;
}
nb = bl[n];
int rt;
memset(ne, -1, sizeof ne);
F(i, 0, n) {
int u, v;
scanf("%d%d", &u, &v);
if (!u) rt = v;
else addEdge(u, v), addEdge(v, u);
}
dfs(rt, -1);
F2(i, 1, n) vv[l[i]] = v[i];
F2(i, 1, n) pre[i] = pre[i-1] + vv[i];
init();
F(i, 0, m) {
int op, l, r;
scanf("%d%d%d", &op,&l,&r);
if (op==1) modify(l, r);
else printf("%llu\n", ask(l, r));
}
return 0;
}

bzoj 4765 普通计算姬 dfs序 + 分块的更多相关文章

  1. BZOJ 4765 普通计算姬 dfs序+分块+树状数组+好题!!!

    真是道好题...感到灵魂的升华... 按dfs序建树状数组,拿前缀和去求解散块: 按点的标号分块,分成一个个区间,记录区间子树和 的 总和... 具体地,需要记录每个点u修改后,对每一个块i的贡献,记 ...

  2. BZOJ 4765 普通计算姬 (分块 + BIT)

    4765: 普通计算姬 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 1547  Solved: 329[Submit][Status][Discus ...

  3. BZOJ 4765: 普通计算姬 [分块 树状数组 DFS序]

    传送门 题意: 一棵树,支持单点修改和询问以$[l,r]$为根的子树的权值和的和 只有我这种不会分块的沙茶不会做这道题吗? 说一点总结: 子树和当然上$dfs$序了,询问原序列一段区间所有子树和,对原 ...

  4. bzoj 4765 普通计算姬(树状数组 + 分块)

    http://www.lydsy.com/JudgeOnline/problem.php?id=4765 很nice的一道题啊(可能是因为卡了n久终于做出来了 题意就是给你一棵带点权的有根树,sum( ...

  5. bzoj 4765: 普通计算姬

    Description "奋战三星期,造台计算机".小G响应号召,花了三小时造了台普通计算姬.普通计算姬比普通计算机要厉害一些 .普通计算机能计算数列区间和,而普通计算姬能计算树中 ...

  6. bzoj 4765: 普通计算姬 主席树+替罪羊树思想

    题目大意: 给定一棵\(n\)个节点的带权树有根树,设\(sum_p\)表示以点\(p\)为根的这棵子树中所有节点的权 计算姬支持下列两种操作: 给定两个整数\(u,v\),修改点\(u\)的权值为\ ...

  7. BZOJ 4765: 普通计算姬 (分块+树状数组)

    传送门 解题思路 树上的分块题,,对于修改操作,每次修改只会对他父亲到根这条链上的元素有影响:对于查询操作,每次查询[l,r]内所有元素的子树,所以就考虑dfn序,进标记一次,出标记一次,然后子树就是 ...

  8. 2018.06.30 BZOJ4765: 普通计算姬(dfs序+分块+树状数组)

    4765: 普通计算姬 Time Limit: 30 Sec Memory Limit: 256 MB Description "奋战三星期,造台计算机".小G响应号召,花了三小时 ...

  9. BZOJ4867 Ynoi2017舌尖上的由乃(dfs序+分块)

    容易想到用dfs序转化为序列上的问题.考虑分块,对每块排序,修改时对于整块打上标记,边界暴力重构排序数组,询问时二分答案,这样k=sqrt(nlogn)时取最优复杂度nsqrt(nlogn)logn, ...

随机推荐

  1. protues7.5安装

    win8 + protues7.5的安装  首先找到解压的文件夹,然后按照一步一步安装,如果中间出现不一样的,请退到前一步重新安装, 注意,安装的目录不要出现中文路径.

  2. 文件/etc/passwd,/etc/shadow,/etc/group

    文件/etc/passwd /etc/shadow /etc/group 计算资源的使用(并不是所有的人都可以用这台计算机的) 权限:访问资源的的能力. 用户:获取资源或者权限的凭证. 用户的容器:关 ...

  3. 中国剩余定理算法详解 + POJ 1006 Biorhythms 生理周期

    转载请注明出处:http://exp-blog.com/2018/06/24/pid-1054/ #include <iostream> #include <cstdio> u ...

  4. 总结 Date 2017.09.23

    总结 Date 2017.09.23 <1>统计数字 某次科研调查时得到了n个自然数,每个数均不超过1500000000(1.5*10^9).已知不相同的数不超过10000个,现在需要统计 ...

  5. 通过aop添加日志管理

    1.使用spring 的 aop 技术切到自定义注解上,所以先创建一个自定义注解类 import java.lang.annotation.*; @Target(ElementType.METHOD) ...

  6. 2037: [Sdoi2008]Sue的小球

    2037: [Sdoi2008]Sue的小球 链接 题解 论文 代码 #include<cstdio> #include<algorithm> #include<cstr ...

  7. loj2174 「FJOI2016」神秘数

    先考虑一下一个集合怎么用 \(O(n)\) 时间求出来,然后用主席树推广到一个序列就可以了.大致思想就是考虑一个数的权值和它前面的数的和的关系. #include <algorithm> ...

  8. <<程序猿健康指南>> 笔记

    序言: 长时间对着电脑工 作.一天下来基本不怎么走动的人,患高血压及 2 型糖尿病的风险远高于其他人群, 这两种疾病会对人体的健康产生长久的严重影响,还会增加心脏病及中风的几率. 前言: 阿米什人(A ...

  9. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

    每一个类对象中都一个对象方法列表(对象方法缓存) 类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存) 方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selec ...

  10. 《Cracking the Coding Interview》——第17章:普通题——题目10

    2014-04-28 23:54 题目:XML文件的冗余度很大,主要在于尖括号里的字段名.按照书上给定的方式进行压缩. 解法:这题我居然忘做了,只写了一句话的注解.用python能够相对方便地实现,因 ...