时间限制:
4000ms 单点时限:
2000ms 内存限制:
256MB
描述
有一个N个节点的树,其中点1是根。初始点权值都是0。
一个节点的深度定义为其父节点的深度+1,。特别的,根节点的深度定义为1。
现在需要支持一系列以下操作:给节点u的子树中,深度在l和r之间的节点的权值(这里的深度依然从整个树的根节点开始计算),都加上一个数delta。
问完成所有操作后,各节点的权值是多少。
为了减少巨大输出带来的开销,假设完成所有操作后,各节点的权值是answer[1..N],请你按照如下方式计算出一个Hash值(请选择合适的数据类型,注意避免溢出的情况)。最终只需要输出这个Hash值即可。
MOD =1000000007; // 10^9 + 7
MAGIC= 12347;
Hash =0;
For i= 1 to N do
Hash = (Hash * MAGIC + answer[i]) mod MOD;
EndFor
输入
第一行一个整数T (1 ≤ T ≤ 5),表示数据组数。
接下来是T组输入数据,测试数据之间没有空行。
每组数据格式如下:
第一行一个整数N (1 ≤ N ≤ 105),表示树的节点总数。
接下来N – 1行,每行1个数,a (1 ≤ a ≤ N),依次表示2..N节点的父亲节点的编号。
接下来一个整数Q(1 ≤ Q ≤ 105),表示操作总数。
接下来Q行,每行4个整数,u, l, r, delta (1 ≤ u ≤ N, 1 ≤ l ≤ r ≤ N, -109 ≤ delta ≤ 109),代表一次操作。
输出
对每组数据,先输出一行“Case x: ”,x表示是第几组数据,然后接这组数据答案的Hash值。
数据范围
小数据:1 ≤ N, Q ≤ 1000
大数据:1 ≤ N, Q ≤ 105
样例解释
点1的子树中有1,2,3三个节点。其中深度在2-3之间的是点2和点3。
点2的子树中有2,3两个节点。其中没有深度为1的节点。
所以,执行完所有操作之后,只有2,3两点的权值增加了1。即答案是0 1 1。再计算对应的Hash值即可。
样例输入
1 3 1 2 2 1 2 3 1 2 1 1 1
样例输出
Case 1: 12348
注意表示为father的时候每个结点的高度并不是可以直接得到的,需要深搜,先贴一下我的错误代码:
#include <stdio.h>
#include <vector>
#include <string.h>
#include <iostream>
#include <memory.h>
using namespace std;
int T, i, j, t, N, Q, u, l, r, delta, d[100002], answer[100002],father;
vector<int> tree[100002];
void dfs(int u, int l, int r, int delta) {
if (d[u] >= l && d[u] <= r)
answer[u] += delta;
for (int i = 0; i < tree[u].size(); ++i) {
dfs(tree[u][i], l, r, delta);
}
}
int main() {
freopen("E:\\练习\\testForVS2012\\test\\test\\in.txt","r",stdin);
const long long MOD =1000000007, MAGIC= 12347;
long long hash = 0;
scanf("%d",&T);
for (t = 1; t <= T; ++t) {
scanf("%d", &N);
memset(d, 0, sizeof(d));
memset(answer, 0, sizeof(answer));
hash = 0;
d[1] = 1;
for (i = 2; i <= N; ++i) {
scanf("%d",&father);
d[i] = d[father] + 1;
tree[father].push_back(i);
}
scanf("%d", &Q);
for (i = 0; i < Q; ++i) {
scanf("%d%d%d%d",&u, &l, &r, &delta);
dfs(u, l, r, delta);
}
for (i = 1; i <= N; ++i)
hash = (hash * MAGIC + answer[i]) % MOD;
printf("Case %d: %lld\n",t,hash);
for (i = 1; i <= N; ++i)
tree[i].clear();
}
return 0;
}
据说如果把上面的d[i] = d[father] + 1改掉我上面暴力的方法都可以通过,但是比较好的方法是用树状数组把查询都先记录下来,然后再深搜一次更新,补充一下树状数组的概念:
假设数组a[1..n],那么查询a[1]+…+a[n]的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别。 来观察这个图:
树状数组的结构图
令这棵树的结点编号为C1,C2…Cn。令每个结点的值为这棵树的值的总和,那么容易发现: C1 = A1 C2 = A1 + A2 C3 = A3 C4 = A1 + A2 + A3 + A4 C5 = A5 C6 = A5 + A6 C7 = A7 C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 … C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16 这里有一个有趣的性质: 设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax, 所以很明显:Cn = A(n – 2^k + 1) + … + An 算这个2^k有一个快捷的办法,定义一个函数如下即可:
1 2 3 | intlowbit(intx){ returnx&(x^(x–1)); } |
利用机器补码特性,也可以写成:
1 2 3 | intlowbit(intx){ returnx&(-x); } |
当想要查询一个SUM(n
)(求a[n]的和),可以依据如下算法即可: step1: 令sum = 0,转第二步; step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步; step3: 令n = n – lowbit(n),转第二步。 可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明: n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。 那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。 所以修改算法如下(给某个结点i加上x): step1: 当i > n时,算法结束,否则转第二步; step2: Ci = Ci + x, i = i + lowbit(i)转第一步。 i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。 对于
数组求和来说树状数组简直太快了!
注: 求lowbit(x)的建议公式: lowbit(x):=x and (x xor (x – 1)); 或lowbit(x):=x and (-x); lowbit(x)即为2^k的值。 最后转一个朋友的代码:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int MAXN = 100000 + 100;
const int MOD = 1000000007;
int qlast[MAXN], last[MAXN];
struct QQ{
int l, r, delta;
int pre;
} q[MAXN];
struct EE {
int y;
int pre;
} e[MAXN];
long long ans[MAXN];
long long hash1;
long long t[MAXN];
int n;
void add(int d, int del)
{
if (d < 1)
return;
d = n - d + 1;
while (d <= n)
{
t[d] += del;
d += (d & (-d));
}
}
long long getsum(int d)
{
d = n - d + 1;
long long ret = 0;
while (d)
{
ret += t[d];
d -= (d & (-d));
}
return ret;
}
void solve(int x, int d)
{
int i = qlast[x];
while (i != 0)
{
add(q[i].r, q[i].delta);
add(q[i].l - 1, -q[i].delta);
i = q[i].pre;
}
ans[x] += getsum(d);
while (last[x] != 0)
{
solve(e[last[x]].y, d + 1);
last[x] = e[last[x]].pre;
}
i = qlast[x];
while (i != 0)
{
add(q[i].r, -q[i].delta);
add(q[i].l - 1, q[i].delta);
i = q[i].pre;
}
}
int main()
{
int T;
freopen("E:\\练习\\testForVS2012\\test\\test\\in.txt","r",stdin);
scanf("%d", &T);
for (int tt = 1; tt <= T; ++tt)
{
for (int i = 1; i <= n; ++i)
{
t[i] = 0;
ans[i] = 0;
qlast[i] = 0;
last[i] = 0;
}
scanf("%d", &n);
for (int i = 2; i <= n; ++i)
{
int p;
scanf("%d", &p);
e[i].y = i;
e[i].pre = last[p];
last[p] = i;
}
int Q;
scanf("%d", &Q);
for (int i = 1; i <= Q; ++i)
{
int x;
scanf("%d%d%d%d", &x, &q[i].l, &q[i].r, &q[i].delta);
q[i].pre = qlast[x];
qlast[x] = i;
}
solve(1, 1);
printf("Case %d: ", tt);
int magic = 12347;
hash1 = 0;
for (int i = 1; i <= n; ++i)
{
hash1 = (hash1 * magic + ans[i]) % MOD;
}
cout << hash1 << endl;
}
return 0;
}