hdu3974 Assign the task(线段树/dfs)(好题)

题目链接:点击链接

题意:

给一棵n个点的树,点标号从1到n。有两种操作,操作1,将树上的某个点以及它的子孙都赋值为某个数;操作2,查询某个点的值。

思路:

做法一:

dfs。更新时,将目标节点更新就行,给节点记录一个时间戳。查询时,向上访问父亲节点,对比儿子节点和父亲节点的时间戳,如果父亲节点的时间戳晚于儿子节点,儿子的值等于父亲的值,反之亦然。

代码:

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

using namespace std;

const int maxn = 50005;

int cnt, cnt_now, ans;
int ord[maxn], fa[maxn], idx[maxn];

void init(){
    memset(ord, -1, sizeof(ord));
    memset(idx, -1, sizeof(idx));
    memset(fa, -1, sizeof(fa));
    cnt = 0;
}

void dfs(int x){
    if(fa[x]==-1){
        if(ord[x] > cnt_now){
            cnt_now = ord[x];
            ans = idx[x];
        }

    }
    else{
        if(ord[x] < ord[fa[x]]){
            ord[x] = ord[fa[x]];
            idx[x] = idx[fa[x]];
        }
        if(ord[x] > cnt_now){
            cnt_now = ord[x];
            ans = idx[x];
        }
        dfs(fa[x]);
    }
}


int main(){
    char opt[10];
    int t, n, a, b, m, Case=0;
    scanf("%d", &t);
    while(t--){
        init();
        scanf("%d", &n);
        for(int i=0; i<n-1; ++i){
            scanf("%d%d", &a, &b);
            fa[a] = b;
        }
        scanf("%d", &m);
        printf("Case #%d:\n", ++Case);
        for(int i=0; i<m; ++i){
            scanf("%s", opt);
            if(opt[0] == 'C'){
                scanf("%d", &a);
                cnt_now = -1; ans = -1;
                dfs(a);
                printf("%d\n", ans);
            }
            if(opt[0] == 'T'){
                scanf("%d%d", &a, &b);
                ord[a] = ++cnt;
                idx[a] = b;
            }
        }
    }
    return 0;
}

做法二:

用dfs序建线段树。思路相当于,把当前节点管理所有儿子节点,转换为当前节点对应一条包含所有儿子的线段,

如何转换呢?dfs的时候,访问到当前节点x后,给节点x一个在线段树中的下标cnt,即节点x在线段树中对应的起点,记录为L[x] =cnt,继续深入,重复之前的操作,cnt不断加1。dfs再次返回到x时,我们知道节点x所管理的儿子在线段树中的终点为cnt,记录为R[x] = cnt,那么x节点对应的线段在线段树中为[L[x], R[x]],这样就完成了树到线段树的映射。

之后就是对应的区间懒惰更新,单点查询。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int maxn = 50005;


int cnt;
int L[maxn], R[maxn];
int tree[maxn<<2];
int lazy[maxn<<2];
int pre[maxn];
vector<int> edges[maxn];

void init(int n){
    cnt = 0;
    memset(pre, 0, sizeof(pre));
    memset(tree, -1, sizeof(tree));
    memset(lazy, -1, sizeof(tree));
    for(int i=1; i<=n; ++i) edges[i].clear(); 
}

void dfs(int rt){
    L[rt] = ++cnt;
    for(int i=0; i<edges[rt].size(); ++i){
        dfs(edges[rt][i]);
    }
    R[rt] = cnt;
}

void pushdown(int rt){
    if(lazy[rt]!=-1){
        tree[rt<<1] = tree[rt<<1|1] = tree[rt];
        lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
        lazy[rt] = -1; 
    }
}

void update(int rt, int a, int b, int l, int r, int x){
    if(a<=l && r<=b){
        tree[rt] = lazy[rt] = x;
    }
    else{
        pushdown(rt);
        int mid = (l+r)>>1;
        if(a<=mid) update(rt<<1, a, b, l, mid, x);
        if(mid<b) update(rt<<1|1, a, b, mid+1, r, x);
    }
}

int query(int rt, int idx, int l, int r){
    if(l==r && l==idx){
        return tree[rt];
    }
    else{
        int mid = (l+r)>>1;
        pushdown(rt);
        if(idx<=mid) return query(rt<<1, idx, l, mid);
        if(mid<idx) return query(rt<<1|1, idx, mid+1, r);
    }
}

int main(){
    char opt[10];
    int t, n, a, b, m, Case=0;
    scanf("%d", &t);
    while(t--){
        scanf("%d", &n);
        init(n);
        for(int i=0; i<n-1; ++i){
            scanf("%d%d", &a, &b);
            edges[b].push_back(a);
            ++pre[a];
        }
        for(int i=1; i<=n; ++i){
            if(pre[i]==0){
                dfs(i);
                break;
            }
        }
        scanf("%d", &m);
        printf("Case #%d:\n", ++Case);
        for(int i=0; i<m; ++i){
            scanf("%s", opt);
            if(opt[0] == 'C'){
                scanf("%d", &a);
                printf("%d\n", query(1, L[a], 1, cnt));
            }
            if(opt[0] == 'T'){
                scanf("%d%d", &a, &b);
                update(1, L[a], R[a], 1, cnt, b);
            }
        }
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值