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

题面

Description

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

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

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

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

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

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

Input

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

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

第二行包含四个整数\(x_1\),\(y_1\),\(x_2\),\(y_2\),表示渔网的左下角坐标和右上角坐标。

接下来\(n\)行,每行两个整数\(x_i\),\(y_i\),表示标号为\(i\)的鱼初始时刻的坐标。

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

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

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

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

\(3\ 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\%\)的数据满足:\(1≤n,m≤1000\)

对于\(100\%\)的数据满足:\(1≤T≤10\),\(1≤n,m≤30000\),\(1≤l≤r≤n\),\(1≤d≤10^9\),\(x_1≤x_2\),\(y_1≤y_2\)。保证任意时刻所有涉及的坐标值在\([?10^9,10^9]\)范围内。

题解

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

  1. \(x<x_1\)或\(y<y_1\),即对于横坐标或纵坐标来言,它可能游到渔网但它不在渔网内。
  2. \(x_1<x≤x_2\)或\(y_1<y≤y_2\),即对于横坐标或纵坐标来言,它在渔网内。
  3. \(x>x_2\)或\(y>y_2\),即对于横坐标或纵坐标来言,它不可能游到渔网。

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

  1. 如果\(x<x_1\),它就可能到达第二个状态,所以它到下一个状态的\(x\)坐标距离为\(x_1-x\)。
  2. 如果 \(x_1<x≤x_2\),它就可能到达第三个状态,所以它到下一个状态的\(x\)坐标距离为\(x_2-x\)。
  3. 如果\(x>x_2\),它就永远无法到达下一个状态,因为没有下一个状态,所以它到下一个状态的\(x\)坐标距离为\(\infty\)。

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

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

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

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

最后询问的时候直接查询树状数组中的\([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;
}

原文地址:https://www.cnblogs.com/ez-lcw/p/11491340.html

时间: 2024-10-27 15:31:33

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

线段树&amp;数状数组

线段树 单点修改,区间查询 #include<bits/stdc++.h> using namespace std; int n,q; long long num[1000010]; struct tree { int l,r; long long sum,max; }t[4000010]; void BuildTree(int,int,int); void Update(int,int,int,int,long long); long long Query(int,int,int,int,i

Vijos P1066 弱弱的战壕【多解,线段树,暴力,树状数组】

弱弱的战壕 描述 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了-_-b. mx在他的基地附近建立了n个战壕,每个战壕都是一个独立的作战单位,射程可以达到无限(“mx不赢定了?!?”永恒[email protected][email protected]). 但是,战壕有一个弱点,就是只能攻击它的左下方,说白了就是横纵坐标都不大于它的点(mx:“我的战壕为什么这么菜”ToT).这样,永恒就可以从别的地方进攻摧毁战壕,从而消灭mx的部队. 战壕都有一个保护范围,同它的攻击

Curious Robin Hood(树状数组+线段树)

1112 - Curious Robin Hood    PDF (English) Statistics Forum Time Limit: 1 second(s) Memory Limit: 64 MB Robin Hood likes to loot rich people since he helps the poor people with this money. Instead of keeping all the money together he does another tri

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

[luogu P3801] 红色的幻想乡 [线段树][树状数组]

题目背景 蕾米莉亚的红雾异变失败后,很不甘心. 题目描述 经过上次失败后,蕾米莉亚决定再次发动红雾异变,但为了防止被灵梦退治,她决定将红雾以奇怪的阵势释放. 我们将幻想乡看做是一个n*m的方格地区,一开始没有任何一个地区被红雾遮盖.蕾米莉亚每次站在某一个地区上,向东南西北四个方向各发出一条无限长的红雾,可以影响到整行/整列,但不会影响到她所站的那个地区.如果两阵红雾碰撞,则会因为密度过大而沉降消失.灵梦察觉到了这次异变,决定去解决它.但在解决之前,灵梦想要了解一片范围红雾的密度.可以简述为两种操

【算法系列学习】线段树vs树状数组 单点修改,区间查询 [kuangbin带你飞]专题七 线段树 A - 敌兵布阵

https://vjudge.net/contest/66989#problem/A 单点修改,区间查询 方法一:线段树 http://www.cnblogs.com/kuangbin/archive/2011/08/15/2139834.html 1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cstring> 5 #include<cmath> 6 #

hdu1540 Tunnel Warfare 线段树/树状数组

During the War of Resistance Against Japan, tunnel warfare was carried out extensively in the vast areas of north China Plain. Generally speaking, villages connected by tunnels lay in a line. Except the two at the ends, every village was directly con

UVA 11990 ”Dynamic“ Inversion(线段树+树状数组)

[题目链接] UVA11990 [题目大意] 给出一个数列,每次删去一个数,求一个数删去之前整个数列的逆序对数. [题解] 一开始可以用树状数组统计出现的逆序对数量 对于每个删去的数,我们可以用线段树求出它在原序列中的逆序对贡献 在线段树的每个区间有序化数据,就可以二分查找出这个数在每个区间的位置, 这样就处理出了划分出的区间的贡献,先用答案减去这一部分 接下来考虑已经删去部分的容斥,我们发现只要对删去部分再做一次类似的操作, 将这一部分跟当前删去数求一次贡献就是刚才多减去的部分,将这部分的答案

士兵杀敌(四)(树状数组+线段树)

士兵杀敌(四) 时间限制:2000 ms  |  内存限制:65535 KB 难度:5 描述 南将军麾下有百万精兵,现已知共有M个士兵,编号为1~M,每次有任务的时候,总会有一批编号连在一起人请战(编号相近的人经常在一块,相互之间比较熟悉),最终他们获得的军功,也将会平分到每个人身上,这样,有时候,计算他们中的哪一个人到底有多少军功就是一个比较困难的事情,军师小工的任务就是在南将军询问他某个人的军功的时候,快速的报出此人的军功,请你编写一个程序来帮助小工吧. 假设起始时所有人的军功都是0. 输入