Vijos P1448 校门外的树(括号序列法)

发布时间 2023-10-15 22:16:23作者: typerxiaozhu

此题如果用常规的线段树或树状数组会非常麻烦,同时不好调试,在此介绍一种优美的做法:

由于本题是一条条线段,所以可以使用括号法:

如果需要在 \(l\sim r\) 区间种树,那么就在 \(l\)\(r\) 上打一个括号表示这个区间里面种了一种树,我们使用两个树状数组 \(tr1,tr2\) 来分别维护左括号和右括号,并且用前缀和记录 \(1\sim i\) 区间里面有多少个左括号或右括号

那么当我们查询某一区间时,需要用 \(1\sim r\) 的种数减去 \(1\sim l-1\) 的种数(前缀和的思想),那么为了做到这个事情可以直接用 \(tr1[r]-tr2[l-1]\),如果在 \(1\sim l-1\) 形成了区间,那么必然有一对括号均在 \(1\sim l-1\) 中,反之则说明这个区间的右端点 \(\geq l\),所以使用 \(1\sim r\) 的左括号数减去 \(1\sim l-1\) 的右括号数就行了

#include <iostream>
using namespace std;
const int N = 5e4+5;
int tr1[N],tr2[N];
int n,m;
int lowbit(int x)
{
    return x&-x;
}
int sum(int tr[],int x)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
void add(int tr[],int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
int main()
{
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int k,l,r;
        scanf("%d%d%d",&k,&l,&r);
        if(k==1) add(tr1,l,1),add(tr2,r,1);
        else printf("%d\n",sum(tr1,r)-sum(tr2,l-1));
    }
    return 0;
}