HDU - 1540 【线段树左右区间合并】

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

题目大概的意思就是求当前这个村庄,左右连续的村庄共有几个,包括自己。

思路:

比较容易想到的就是把用线段树划分的每一个区间的左右连续区间长度记录下来,然后尝试着吧X这个村庄的左右连续并且没被摧毁的村庄个数连起来,就能得出答案了。

具体看代码的注释

#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 a[Maxn], Left[4*Maxn], Right[4*Maxn], zero[4*Maxn], X; // zero用来记录当前区间有几个被摧毁的村庄
bool L, R, op; // L, R分别表示左右界有没有找到,op == 1 表示摧毁

void init(int cur, int l, int r) {
    if (l == r) {
        if(a[l]) Left[cur] = Right[cur] = 1;
        else zero[cur] = 1;
        return;
    }
    int mid = (r+l)/2;
    init(cur*2, l, mid); init(cur*2+1, mid+1, r);
    Left[cur] = Left[cur*2]; Right[cur] = Right[cur*2+1]; // 如果左子树全都是1,那么当前区间的左连续长度还需要
    if (!zero[cur*2]) Left[cur] += Left[cur*2+1];           // 加上右子树的左连续区间,以此类推
    if (!zero[cur*2+1]) Right[cur] += Right[cur*2];             // 通过zero能判断当前区间是否全都是1
}

void updata(int cur, int l, int r) {
    if (l == r) {
        if (X == l) {
            if (op && a[l]) {   // op == 1, 表示摧毁村庄
                a[l] = 0; zero[cur] = 1;
                Left[cur] = Right[cur] = 0;
            } else if (!op && !a[l]) {
                a[l] = 1; zero[cur] = 0;
                Left[cur] = Right[cur] = 1;
            }
        }
        return;
    }

    int mid = (r+l)/2;
    if (X <= mid) updata(cur*2, l, mid); // 如果修改的是左子树,右子树可以不用递归,但是右子树的左连续区间可能会
    else updata(cur*2+1, mid+1, r);         // 因为左子树的修改而发生变化,所以右子树的区间记录也要更新
    Left[cur] = Left[cur*2]; Right[cur] = Right[cur*2+1];
    if (!zero[cur*2]) Left[cur] += Left[cur*2+1];
    if (!zero[cur*2+1]) Right[cur] += Right[cur*2];
    zero[cur] = zero[cur*2]+ zero[cur*2+1];
}

int solve(int cur, int l, int r) {
    if (l == r) {
        return Left[cur];
    }
    int m1, m2, mid = (r+l)/2;
    if (X <= mid) {                     // 如果当前是左子树,
        m1 = solve(cur*2, l, mid);         // 如果找到右界,或者左右界都找到了,那么就不需要右子树的左连续区间了,
        m2 = Left[cur*2+1];                 // 否则要加上右子树的左连续区间,并且判断右子树有没有被摧毁的村庄,
        if (R) return m1;                   // 如果有,那么说明找右界了。下面的右子树也是同理。
        else {
            if (zero[cur*2+1]) R = true;
            return m1+m2;
        }
    } else {
        m1 = solve(cur*2+1, mid+1, r);
        m2 = Right[cur*2];
        if (L) return m1;
        else {
            if (zero[cur*2]) L = true;
            return m1+m2;
        }
    }
}

int main (void)
{
    int N, M;
    while(scanf ("%d%d", &N, &M) != EOF) {
        for (int i = 1; i <= N; ++i) a[i] = 1;
        memset(Left, 0, sizeof(Left));
        memset(Right, 0, sizeof(Right));
        memset(zero, 0 ,sizeof(zero));
        init(1, 1, N);
        char ch; stack<int> st;

        while (M--) {
            scanf(" %c", &ch);
            if (ch == 'R') {
                op = false; X = st.top(); st.pop();
                updata(1, 1, N);
            } else if (ch == 'D') {
                op = true; scanf("%d", &X);
                st.push(X); updata(1, 1, N);
            } else {
                scanf("%d", &X);
                L = R = false;
                if(!a[X]) printf("0\n");
                else printf("%d\n", solve(1, 1, N));
            }
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值