HDU 3887 Counting Offspring dfs序的运用 | 非递归

2 篇文章 0 订阅
2 篇文章 0 订阅

题目链接

一天时间做了两道 dfs序 的题, 都是 WA 不停,最后一道改成 100W的数组,一道改成双向边就AC了。。能不能再坑一点的。。


好,继续说这道题。。

上一道题苹果树是统计叶子节点的和,这道题是统计叶子节点中比自己权值小的和


思路

首先按照上一道题的惯性思路,会觉得很复杂。因为再算和的时候要额外判断一下大小。由于树状数组是预处理的,显然无法再单独判断,于是就不知如何是好。

后来想到,能不能在 dfs 中记录一下比自己小的数。


如何统计?

第一会想到 DP 一样的东西,如果比子节点大,直接加上,如果小的话,貌似跪了。。。

突然想到以前有过一次 在树状数组的建树过程中动态维护区间和,觉得可以试试。

于是就有了这样的算法:


在dfs过程中,第一次(也是唯一一次)碰到一个点 v 时, visit[i] = 1. 此时,pre[v] = visit[0,v) 的和也就是比v小的数的个数!因为 dfs序的性质,现在计算的和是不含它的子节点的,当一个点dfs过后,再计算一下s = visit[0,v) 的和,它表示的便是包含子节点在内的所有比 v 小的点的和。因为dfs序的性质,s[v]-pre[v] 便是子节点中比自己小的点的权值的和。

有木有发现,其实连进出的时间戳都没有记录!只是在进出栈时就把ans计算出来了。


另外比较坑的就是如果用 递归写法会耗费很多堆栈,不得已改用非递归写法。


上代码:

#include <cstdio>
#include <stack>
#define N 201010
using namespace std;

stack<int> st;
struct NODE {
    int v,next;
} a[N],e[N];
int tr[N],pre[N],s[N],b[N];
int n,em,root,tt;

void update(int x) {
    while ( x<=n) {
        tr[x] += 1;
        x += x & -x;
    }
}

int gs(int x) {
    int s = 0;
    while (x>0) {
        s += tr[x];
        x -= x & -x;
    }
    return s;
}

void joint(int a1,int a2){
    e[++em].v = a2;
    e[em].next = a[a1].next;
    a[a1].next = em;
}

int main() {
    while ( ~scanf("%d%d",&n,&root)) {
        if ( !n && !root ) break;
        for ( int i(1); i<=n; i++) tr[i] = a[i].next = b[i] = pre[i]= 0;
        em = 0;
    	tt = 0;

        for ( int i(1); i<n; i++) {
            int a1,a2;
            scanf("%d%d",&a1,&a2);
            joint(a1,a2);
            joint(a2,a1);
        }
        
        while (!st.empty()) st.pop();   // dfs
        st.push(root);
        while (!st.empty()) {
            int v = st.top();
            if ( !b[v] ){
            	b[v] = 1;
            	pre[v] = gs(v);
            	update(v);
	            for ( int p(a[v].next); p; p=e[p].next) {
	                if ( !b[e[p].v] ) {
	                    int u = e[p].v;
	                    st.push(u);
	                }
	            }
            }
            if ( st.top() == v ) {
                s[v] = gs(v-1) - pre[v];
                st.pop();
            }
        }
        for ( int i(1); i<n; i++) printf("%d ",s[i]);
        printf("%d\n",s[n]);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值