编程之美初赛第一场 树

时间限制:
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;
}

    原文作者:taoqick
    原文地址: https://blog.csdn.net/taoqick/article/details/24131537
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞