【XSY2569】火神的鱼(线段树+树状数组)

题面

Description

火神最爱的就是吃鱼了,所以某一天他来到了一个池塘边捕鱼。池塘可以看成一个二维的平面,而他的渔网可以看成一个与坐标轴平行的矩形。

池塘里的鱼不停地在水中游动,可以看成一些点。有的时候会有鱼游进渔网,有的时候也会有鱼游出渔网。所以火神不知道什么时候收网才可以抓住最多的鱼,现在他寻求你的帮助。

他对池塘里的每条鱼都给予了一个标号,分别从 1 1 1 n n n标号, n n n表示池塘里鱼的总数。鱼的游动可以概括为两个动作:

1   l   r   d 1\ l\ r\ d 1 l r d : 表示标号在 [ l , r ] [l,r] [l,r]这个区间内的鱼向 x x x轴正方向游动了 d d d个单位长度。

2   l   r   d 2\ l\ r\ d 2 l r d:表示标号在 [ l , r ] [l,r] [l,r]这个区间内的鱼向 y y y轴正方向游动了 d d d个单位长度。

在某些时刻,火神会询问你现在有多少条鱼在渔网内(边界上的也算),请你来帮助他吧。

Input

第一行包含一个整数 T T T,表示测试数据组数。对于每组测试数据:

第一行包含一个整数 n n n,表示鱼的总数。

第二行包含四个整数 x 1 x_1 x1, y 1 y_1 y1, x 2 x_2 x2, y 2 y_2 y2,表示渔网的左下角坐标和右上角坐标。

接下来 n n n行,每行两个整数 x i x_i xi, y i y_i yi,表示标号为 i i i的鱼初始时刻的坐标。

再接下来一行包含一个整数 m m m,表示后面的事件数目。

再接下来的 m m m行,每行为以下三种类型的一种:

1   l   r   d 1\ l\ r\ d 1 l r d : 表示标号在 [ l , r ] [l,r] [l,r]这个区间内的鱼向 x x x轴正方向游动了 d d d个单位长度。

2   l   r   d 2\ l\ r\ d 2 l r d:表示标号在 [ l , r ] [l,r] [l,r]这个区间内的鱼向 y y y轴正方向游动了 d d d个单位长度。

3   l   r 3\ l\ r 3 l r : 表示询问现在标号在 [ l , r ] [l,r] [l,r]这个区间内的鱼有多少在渔网内。

Output

对于每组数据的每个询问,输出一个整数表示对应的答案。

Sample Input

1
5
1 1 5 5
1 1
2 2
3 3
4 4
5 5
3
3 1 5
1 2 4 2
3 1 5

Sample Output

5
4

HINT

对于 30 % 30\% 30%的数据满足: 1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000

对于 100 % 100\% 100%的数据满足: 1 ≤ T ≤ 10 1≤T≤10 1T10 1 ≤ n , m ≤ 30000 1≤n,m≤30000 1n,m30000 1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn 1 ≤ d ≤ 1 0 9 1≤d≤10^9 1d109 x 1 ≤ x 2 x_1≤x_2 x1x2 y 1 ≤ y 2 y_1≤y_2 y1y2。保证任意时刻所有涉及的坐标值在 [ − 1 0 9 , 1 0 9 ] [−10^9,10^9] [109,109]范围内。

题解

这道题的关键在于审题。我们看到每条鱼只可能向 x x x轴正方向或 y y y轴正方向游,而且游的距离 d d d为一正数,就可得鱼的坐标都是单调变化的,所以一旦某条鱼的 x x x坐标大于渔网的右上角的 x x x坐标,或 y y y坐标大于渔网的右上角的 y y y坐标,它就永远游不进渔网。所以我们用一颗线段树维护所有鱼的 x x x坐标,一颗线段树维护所有鱼的 y y y坐标,并把鱼的位置分为三个状态:(设鱼的坐标为 ( x , y ) (x,y) (x,y),渔网左下角坐标为 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),渔网右上角坐标为 ( x 2 , y 2 ) (x_2,y_2) (x2,y2),下面的都如此)

  1. x &lt; x 1 x&lt;x_1 x<x1 y &lt; y 1 y&lt;y_1 y<y1,即对于横坐标或纵坐标来言,它可能游到渔网但它不在渔网内。

  2. x 1 &lt; x ≤ x 2 x_1&lt;x≤x_2 x1<xx2 y 1 &lt; y ≤ y 2 y_1&lt;y≤y_2 y1<yy2,即对于横坐标或纵坐标来言,它在渔网内。

  3. x &gt; x 2 x&gt;x_2 x>x2 y &gt; y 2 y&gt;y_2 y>y2,即对于横坐标或纵坐标来言,它不可能游到渔网。

那么我们对于 x x x坐标线段树中的某个叶子节点,设它所代表的是第 k k k条鱼,我们储存这条鱼到下一个状态的 x x x坐标距离。那么:

  1. 如果 x &lt; x 1 x&lt;x_1 x<x1,它就可能到达第二个状态,所以它到下一个状态的 x x x坐标距离为 x 1 − x x_1-x x1x

  2. 如果 x 1 &lt; x ≤ x 2 x_1&lt;x≤x_2 x1<xx2,它就可能到达第三个状态,所以它到下一个状态的 x x x坐标距离为 x 2 − x x_2-x x2x

  3. 如果 x &gt; x 2 x&gt;x_2 x>x2,它就永远无法到达下一个状态,因为没有下一个状态,所以它到下一个状态的 x x x坐标距离为 ∞ \infty

然后,对于 x x x坐标线段树中的某个非叶子节点,设它所代表的是第 l ∼ r l\sim r lr条鱼,我们就储存这个区间里所有鱼到下一个状态的最小值 m i n n [ u ] minn[u] minn[u]

然后对于每一次的修改操作,我们找到对应的树,这里以修改 x x x坐标为例,那我们就对 x x x树进行修改。我们先按普通线段树的方法找到对应区间,然后由于这个区间内的所有鱼都往 x x x轴正方向游了 d d d个单位,所以它们对应的距离下一个状态的 x x x坐标距离会减少 d d d,所以 m i n n [ u ] minn[u] minn[u]要减去 d d d。这时,如果 m i n n [ u ] &gt; 0 minn[u]&gt;0 minn[u]>0,就说明没有鱼进入下一个状态。否则,就找到是哪些鱼进入了下一个状态并对它进行修改,寻找过程就是访问左右儿子的 m i n n minn minn值,如果某个儿子的 m i n n ≤ 0 minn≤0 minn0,那么就递归继续寻找。最后如果递归寻找到了叶子节点,就修改对应坐标并更新距离就好了。

然后对于每一次对某个叶子节点的坐标修改,看它当前在不在渔网内(同时检验 x x x y y y),若在并且修改前不在,就在树状数组的对应位置加 1 1 1

对于 y y y树的各种操作也是如此。

最后询问的时候直接查询树状数组中的 [ l , r ] [l,r] [l,r]区间和就好了。

最后的代码如下:

#include<bits/stdc++.h>
 
#define N 30010
#define INF 0x7fffffff
 
using namespace std;
 
struct Point//作者比较喜欢用结构体存平面上的点,有些喜欢用数组的人别介意
{
    int x,y;
    void read(){scanf("%d%d",&x,&y);}
    int get(int opt){return opt?y:x;}//get是根据opt取当前这条鱼对应的x或y坐标
    void add(int opt,int val){if(opt) y+=val;else x+=val;}//add是根据opt为当前这条鱼对应的x或y坐标加上val
}fish[N],st,ed;
 
int T,n,m,c[N]; 
bool innet[N];//记录每一条鱼在不在网内的

//树状数组部分:start
int lowbit(int x)
{
    return x&-x;
}
 
void add(int x,int y)
{
    for(;x<=n;x+=lowbit(x))c[x]+=y;
}
 
int sum(int x)
{
    int ans=0;
    for(;x;x-=lowbit(x))ans+=c[x];
    return ans;
}
//end
 
bool check(Point p)//判断某个点是否在渔网内
{
    return st.x<=p.x&&p.x<=ed.x&&st.y<=p.y&&p.y<=ed.y;
}
 
struct Segment_Tree
{
    int opt,lazy[N<<2],minn[N<<2];//opt是用来记录这棵线段树是x树还是y树
    void change(int x,int k)//修改
    {
        if(innet[x])
            add(x,-innet[x]);//先减掉原来的
        if((innet[x]=check(fish[x])))
            add(x,innet[x]);//再加上更新后的
        int now=fish[x].get(opt),x1=st.get(opt),x2=ed.get(opt);
        if(now<x1)//第一种状态
            return void(minn[k]=x1-now);
        if(now<=x2)//第二种状态
            return void(minn[k]=x2-now);
        minn[k]=INF;//第三种状态
    }
    void up(int k)
    {
        minn[k]=min(minn[k<<1],minn[k<<1|1]);
    }
    void down(int k)
    {
        if(lazy[k])//懒标记
        {
            lazy[k<<1]+=lazy[k],lazy[k<<1|1]+=lazy[k];
            minn[k<<1]-=lazy[k],minn[k<<1|1]-=lazy[k];
            lazy[k]=0;
        }
    }
    void build(int k,int l,int r)//建树
    {
        lazy[k]=0;
        if(l==r)
        {
            change(l,k);
            return;
        }
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        up(k);
    }
    void find(int k,int l,int r)//查找
    {
        if(l==r)
        {
            fish[l].add(opt,lazy[k]);
            lazy[k]=0;
            change(l,k);
            return;
        }
        down(k);
        int mid=(l+r)>>1;
        if(minn[k<<1]<=0)find(k<<1,l,mid);
        if(minn[k<<1|1]<=0)find(k<<1|1,mid+1,r);
        up(k);
    }
    void update(int k,int l,int r,int ql,int qr,int val)//更改
    {
        if(ql<=l&&r<=qr)
        {
            lazy[k]+=val;
            minn[k]-=val;
            if(minn[k]<=0) 
                find(k,l,r);
            return;
        }
        down(k);
        int mid=(l+r)>>1;
        if(ql<=mid)update(k<<1,l,mid,ql,qr,val);
        if(qr>mid)update(k<<1|1,mid+1,r,ql,qr,val);
        up(k);
    }
}treex,treey;
 
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        memset(c,0,sizeof(c));
        memset(innet,0,sizeof(innet));
        scanf("%d",&n);
        st.read(),ed.read();
        treex.opt=0,treey.opt=1;
        for(int i=1;i<=n;i++)
            fish[i].read();
        treex.build(1,1,n);
        treey.build(1,1,n);
        scanf("%d",&m);
        while(m--)
        {
            int opt;
            scanf("%d",&opt);
            if(opt==1)
            {
                int l,r,d;
                scanf("%d%d%d",&l,&r,&d);
                treex.update(1,1,n,l,r,d);
            }
            if(opt==2)
            {
                int l,r,d;
                scanf("%d%d%d",&l,&r,&d);
                treey.update(1,1,n,l,r,d);
            }
            if(opt==3)
            {
                int l,r;
                scanf("%d%d",&l,&r);
                printf("%d\n",sum(r)-sum(l-1));
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值