编程之美初赛第一场 树

来源:互联网 发布:js 过滤换行符 编辑:程序博客网 时间:2024/04/30 11:07

时间限制: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值即可。




样例输入
131221 2 3 12 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;}





0 0
原创粉丝点击