• POJ 3321 Apple Tree 树状数组+DFS


    题意:一棵苹果树有n个结点,编号从1到n,根结点永远是1。该树有n-1条树枝,每条树枝连接两个结点。已知苹果只会结在树的结点处,而且每个结点最多只能结1个苹果。初始时每个结点处都有1个苹果。树的主人接下来会进行m个操作。操作共两种。C X表示将结点x上的苹果数量改变,原本是1,则现在为0,原本是0,现在是1。Q X表示一次查询。要求输出结点X和其子树上的苹果总数。n和m最大可到100000。

    操作只有更新和查询两种,树状数组最合适了。

    首先是树状数组的相关知识。网上有很多讲解,在这里传送一个讲解的地址 传送门

    树状数组最重要的就是要搞明白那种经典的图,之后就没什么问题了。

    思路:本题的关键是如何将树映射成线性的数组。而且树状数组一般是对连续区间求和,又依照题意的要求,树的子树要在区间内也是连续存储的。这里的方法是,采用dfs对树进行一次遍历,树的每一个结点都有st和ed两个时间戳,分别记录该结点被遍历到的时间戳以及它和它的子树全部遍历完后的时间戳。举一个例子来说明。

                            

    依次遍历到的结点:1  5  4  3  2

         对应的时间戳:1  2  3  4  5

    拿结点4来说,它的开始时间戳st为3,结束时间戳ed为5。

    这样的话,假如需要询问结点x和它子树上的苹果总数,只需对区间[st[x], ed[x]]求和即可。另外需要注意的是,树状数组求和函数query求的是区间[1, x]的和,因此要实现之前的求和,需要用query(ed[x]) - query(st[x] - 1)。 (query(0) = 0)

    以上就是解题思路了。

    此外要注意,在建图的时候,添加边应该是双向边(即无向边),不然在遍历时会出现遍历不到或者其他问题。一开始我提交了两次总是tle,问题就在这里。

    至于树状数组更新的时候,假设更新位置为x,则应将后续的x += lowbit[x]的位置也更新,直到x大于n。做这题时,我以为只要更新到结点x的结束位置ed[x]即可,但基于树状数组的特点,x变化后,后续结点即使不在x的子树里也是有可能受影响的,应当更新。不然在求和时就会得出错误结果。

     1 #include<stdio.h>
     2 #include<string.h>
     3 #define maxn 100020
     4 #define maxp 200020
     5 struct node
     6 {
     7     int v;
     8     int next;
     9 }edge[maxp];
    10 int num_edge, head[maxn];
    11 void addedge(int a, int b)
    12 {
    13     edge[num_edge].v = b;
    14     edge[num_edge].next = head[a];
    15     head[a] = num_edge++;
    16 }
    17 void init_edge()
    18 {
    19     num_edge=0;
    20     memset(head,-1,sizeof(head));
    21 }
    22 
    23 int st[maxn], ed[maxn], vis[maxn], cnt;//cnt记录时间戳,初始为0
    24 void get_timestamp(int u)
    25 {
    26     vis[u] = 1;
    27     st[u] = ++cnt;//记录开始时间戳
    28     for (int i = head[u]; i != -1; i = edge[i].next)
    29     {
    30         int v = edge[i].v;
    31         if (!vis[v]) get_timestamp(v);
    32     }
    33     ed[u] = cnt;//记录结束时间戳
    34 }
    35 
    36 int lowbit[maxn], apple[maxn];
    37 int n;//fork的总数
    38 void update(int x,int num)
    39 {
    40     for (int i = x; i <= n; i += lowbit[i])
    41         apple[i] += num;
    42 }
    43 int query(int x)
    44 {
    45     int res = 0;
    46     for (int i = x; i > 0; i -= lowbit[i])
    47         res += apple[i];
    48     return res;
    49 }
    50 int main()
    51 {
    52     //freopen("data.in", "r", stdin);
    53     scanf("%d",&n);
    54     init_edge();
    55     for (int i = 1; i < n; i++)
    56     {
    57         int u, v;
    58         scanf("%d%d",&u,&v);
    59         addedge(u, v);
    60         addedge(v, u);
    61     }
    62     cnt = 0;
    63     memset(vis, 0, sizeof(vis));
    64     get_timestamp(1);
    65     for (int i = 1; i <= n; i++)
    66         lowbit[i] = i & (i ^ (i - 1));
    67     for (int i = 1; i <= n; i++)
    68         update(i, 1);
    69     int m;
    70     scanf("%d",&m);
    71     while (m--)
    72     {
    73         char op;
    74         int x;
    75         getchar();
    76         scanf("%c %d",&op,&x);
    77         if (op == 'Q')
    78             printf("%d
    ",query(ed[x]) - query(st[x] - 1));
    79         else
    80         {
    81             if (query(st[x]) - query(st[x] - 1) == 1)
    82                 update(st[x], -1);
    83             else update(st[x], 1);
    84         }
    85     }
    86     return 0;
    87 }
  • 相关阅读:
    装饰器
    异常处理与断言
    例子:对象构造函数指定类型传入参数(描述符与装饰器的应用)
    Python的描述符
    全新开始fighting
    函数相关知识
    集合的介绍以及简单方法
    列表,元组,字典类的常见简单方法
    Python简单字符串函数介绍
    聚合函数及分组查询及F&Q
  • 原文地址:https://www.cnblogs.com/fenshen371/p/3222025.html
Copyright © 2020-2023  润新知