HDU 3974 【线段树?】

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3974

虽然是抱着线段树的思想去写的代码,但是代码整体看起来并不像线段树。

题目很明确的告诉你,整体的结构是一个树,但是如果不自己多加点条件,很难能在这个树上以O(log)的时间去查找你想要找的数据。

这里有一个存储树的方法,一个数组记录节点的父节点,一个数组记录节点的子节点中最左边的节点(长子),一个数组记录节点右边相邻的兄弟节点,不仅是二叉树,多叉树也同样适用。

当给一个节点分配任务的时候,要做两件事情,第一是把这个节点的所有之前分配过任务的节点的任务编号都设置为 -1,也就是初始化,那我们要找一个节点正在做什么任务的时候应该怎么找,从我们要找的这个节点开始,不断的访问父节点,当父节点的值是大于-1 (也就是有正在执行的任务)的时候,这个值就是我们要找的节点正在执行的任务。之所以这么做是为了解决新分配的任务和旧分配的任务混在一起的问题,导致查找的数据不正确(类似于线段树懒标记的操作,只不过传统的线段树的懒标记是自上而下,上面的节点重置,下面的节点更新,这里正好相反,上面的更新,下面的重置)

如果我们不做任何标记,要给当前节点的所有子节点重置为-1,那无疑是非常耗时间的。所以我们必须要明确向下遍历的路线。

当给一个节点分配任务的同时,需要考虑,如果以后这个节点的某个祖先节点分配新任务的时候,怎样才能让祖先节点更有效率的找到自己,并且把自己当前的任务设置为-1?

这时候就需要我们给祖先节点做一些标记。给当前节点分配任务的时候,不断的访问父节点,并且只要这个父节点没有被分配过任务,那么就给这个原本是-1的节点,设置为-2(只要设置的数字不在任务编号的范围内就不会影响我们查找节点正在执行的任务编号),设置-2这个标记的终止条件是祖先节点有分配的任务。这样的标记能让节点更有方向的去给子节点重置任务编号。

比如我要给节点x分配任务,我们只需要看自己的下一层子节点任务编号,如果是-1,表明下面不会有已经有任务的节点,如果是-2,就表示从这走,能够找到已经有任务编号的节点,这种有方向性的遍历能为我们省下不少时间。

下面是代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <stack>

using namespace std;

typedef long long ll;

const int Maxn = 5e4+10;
const int INF = 0x3f3f3f3f;

int Set[Maxn], f[Maxn], nex[Maxn], Right[Maxn];
// Set是任务的编号,只要大于-1,就说明有正在执行的任务

void updata(int cur) {
    int r = cur;
    while (r != -1) {
        if (Set[r] != -1) { // 只要不是-1,可能是-2,也可能是大于-1的,就重置任务编号,并且继续
            Set[r] = -1; updata(nex[r]); // 向下遍历
        }
        r = Right[r];
    }
}

int main (void)
{
    int T;
    scanf ("%d", &T);
    for (int cas = 1; cas <= T; ++cas) {
        int N, M, u, v;
        scanf("%d", &N);
        memset(Set, -1, sizeof(Set));
        memset(f, -1, sizeof(f));
        memset(nex, -1, sizeof(nex));
        memset(Right, -1, sizeof(Right));

        for (int i = 1; i < N; ++i) {
            scanf("%d%d", &u, &v);
            f[u] = v;
            if (nex[v] == -1) nex[v] = u;
            else {
                int r = nex[v];
                while(1) {
                    if (Right[r] == -1) {
                        Right[r] = u; break;
                    }
                    r = Right[r];
                }
            }
        }

        char ch; int x, y;
        scanf ("%d", &M);
        printf("Case #%d:\n", cas);
        while (M--) {
            scanf(" %c", &ch);
            if (ch == 'C') {
                scanf("%d", &x);
                int ans = Set[x], tmp = f[x];
                while(ans < 0 && tmp > 0) {  // 不断的找节点的父节点,直到找到某个祖先节点存在任务编号
                    ans = Set[tmp]; tmp = f[tmp]; // 这个编号就是我们要找的这个节点正在执行的任务
                }
                if (ans < 0) printf("-1\n");
                else printf("%d\n", ans);
            } else {
                scanf("%d%d", &x, &y);
                Set[x] = y;
                int tmp = f[x];
                while (Set[tmp] == -1 && tmp > 0) { // 向上给祖先节点做标记,方便祖先节点找到自己
                    Set[tmp] = -2; tmp = f[tmp];
                }
                updata(nex[x]);
            }
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值