XSY2569火神的鱼(线段树+树状数组)
Posted ez-lcw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)),下面的都如此)
(x<x_1)或(y<y_1),即对于横坐标或纵坐标来言,它可能游到渔网但它不在渔网内。
(x_1<x≤x_2)或(y_1<y≤y_2),即对于横坐标或纵坐标来言,它在渔网内。
(x>x_2)或(y>y_2),即对于横坐标或纵坐标来言,它不可能游到渔网。
那么我们对于(x)坐标线段树中的某个叶子节点,设它所代表的是第(k)条鱼,我们储存这条鱼到下一个状态的(x)坐标距离。那么:
如果(x<x_1),它就可能到达第二个状态,所以它到下一个状态的(x)坐标距离为(x_1-x)。
如果 (x_1<x≤x_2),它就可能到达第三个状态,所以它到下一个状态的(x)坐标距离为(x_2-x)。
如果(x>x_2),它就永远无法到达下一个状态,因为没有下一个状态,所以它到下一个状态的(x)坐标距离为(infty)。
然后,对于(x)坐标线段树中的某个非叶子节点,设它所代表的是第(lsim 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
",sum(r)-sum(l-1));
}
}
}
return 0;
}
以上是关于XSY2569火神的鱼(线段树+树状数组)的主要内容,如果未能解决你的问题,请参考以下文章