线段树之区间合并
第二次写博客,感觉熟练多了,谢谢大家的支持,我以后也会加油的!
这次延续我以往的代码风格,喜欢我的博客记得留言哦(^ω^)!
这次的题目是一个区间合并的题目,感觉有点不好理清,我会努力的把它说清楚的!
Question:
Description:
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 connected with two neighboring ones.
Frequently the invaders launched attack on some of the villages and destroyed the parts of tunnels in them. The Eighth Route Army commanders requested the latest connection state of the tunnels and villages. If some villages are severely isolated, restoration of connection must be done immediately!
Input:
The first line of the input contains two positive integers n and m (n, m ≤ 50,000) indicating the number of villages and events. Each of the next m lines describes an event.
There are three different events described in different format shown below:
D x: The x-th village was destroyed.
Q x: The Army commands requested the number of villages that x-th village was directly or indirectly connected with including itself.
R: The village destroyed last was rebuilt.
Output:
Output the answer to each of the Army commanders’ request in order on a separate line.
Sample Input:
7 9
D 3
D 6
D 5
Q 4
Q 5
R
Q 4
R
Q 4
Sample Output:
1
0
2
4
Answer:
第一步、数据预处理:
#define MAX 50010
第二步、自定义数据类型:
我仍旧使用结构体数组表示线段树,结构体的成员包括该节点的区间范围,该区间内从左开始向右的的最大连续村庄数,从右开始向左的最大连续村庄数,以及该区间的最大连续村庄数。为什么要这么设置呢?根据题意我们知道,当前区间的最大连续村庄数有三种可能的情况:
1、左子树的最大连续村庄数
2、右子树的最大连续村庄数
3、左子树从右开始向左的最大连续村庄数+右子树从左开始向右的最大连续村庄数
因此,我们还需要保存该区间内从左开始向右的的最大连续村庄数,从右开始向左的最大连续村庄数。
题目还要求我们修复最近毁灭的村庄,就需要保存最近修复的村庄的位置,由于可能出现连续多次修复的情况,需要保存的位置也会有多个,进一步思考发现修复村庄的顺序和毁灭村庄的顺序满足先进后出的原则,因此栈是最适合的数据结构。这里我用数组来模拟一个栈,也可以采用其他方式来实现,比如C++STL的容器。
结构体定义如下:
struct Tree
{
int left,right;
int lsum,rsum,tsum;
}tr[MAX<<2];
栈的定义如下:
int stack[MAX],top=-1;//用数组来模拟栈
第三步、main(),程序结构设计:
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)==2)//一如既往的支持多组输入
{
build(1,1,n);//build()用于建立线段树,无返回值,参数有根节点(id=1),以及此节点表示的区间[l,r]
while(m--)
{
char order[2];
scanf("%s",order);
if(order[0]=='D')//毁灭一个村庄
{
int pos;
scanf("%d",&pos);
updata(1,pos,1);//updata()用于数据更新,无返回值,参数有三个,分别是根节点(id=1),要更新的位置和更新的方式,这里有两种,1代表毁坏一个村庄,2代表修复一个村庄
stack[++top]=pos;//将这个位置保存下来,入栈,也就是记录下次修复是的位置
}
else if(order[0]=='R')//修复一个村庄
{
int pos;
if(top>-1)//栈不为空
{
pos=stack[top--];//读取最近一次被毁灭的村庄
updata(1,pos,2);//数据更新,这里第三个参数变为2,代表是修复村庄
}
}
else
{
int pos;
scanf("%d",&pos);
printf("%d\n",query(1,pos));//query()用于查询,参数为根节点(id=1),和要查询的位置,返回值是在要查询位置的最大连续村庄数
}
}
}
return 0;
}
第四步、子函数的实现:
线段树的建立及其初始化:
//build()用于建立线段树,无返回值,参数有根节点(id),以及此节点表示的区间[l,r]
void build(int id,int l,int r)
{
tr[id].left=l;
tr[id].right=r;
tr[id].lsum=tr[id].rsum=tr[id].tsum=(r-l+1);//初始化因为村庄都是完好的所以这个区间的左最大连续区间数、右最大连续区间数以及该区间连续最大数都是区间长度呢
if(l<r)//没有到叶子节点
{
int mid=(l+r)/2;
build(id*2,l,mid);//建立左子树
build(id*2+1,mid+1,r);//建立右子树
}
}
线段树的更新(单点更新):
//updata()用于数据更新,无返回值,参数有三个,分别是根节点(id),要更新的位置和更新的方式,这里有两种,1代表毁坏一个村庄,2代表修复一个村庄
void updata(int id,int pos,int memthod)
{
if(tr[id].left==tr[id].right)//要更新的村庄
{
if(memthod==1)//要毁灭这个村庄
tr[id].lsum=tr[id].rsum=tr[id].tsum=0;
else//修复这个村庄
tr[id].lsum=tr[id].rsum=tr[id].tsum=1;
}
else//搜寻要更新的村庄
{
int mid=(tr[id].left+tr[id].right)/2;
if(pos<=mid)
updata(id*2,pos,memthod);
else
updata(id*2+1,pos,memthod);
//每次更新也要更新它所在的区间的连续村庄数哦
tr[id].lsum=tr[id*2].lsum;
tr[id].rsum=tr[id*2+1].rsum;
tr[id].tsum=max(tr[id*2].rsum+tr[id*2+1].lsum,max(tr[id*2].tsum,tr[id*2+1].tsum));
//初始化回溯更新,但不要忘记还有两种特殊情况哦
if(tr[id*2].lsum==(tr[id*2].right-tr[id*2].left+1))//如果左子树全都是好的,那么它的父节点的左最大连续区间数还要加上右子树的左最大连续区间数
tr[id].lsum+=tr[id*2+1].lsum;
if(tr[id*2+1].rsum==(tr[id*2+1].right-tr[id*2+1].left+1))//同理,右子树也一样
tr[id].rsum+=tr[id*2].rsum;
}
}
查询函数:
//query()用于查询,参数为根节点(id=1),和要查询的位置,返回值是在当前位置的最大连续村庄数
int query(int id,int pos)
{
if(tr[id].left==tr[id].right||tr[id].tsum==0||tr[id].tsum==(tr[id].right-tr[id].left+1))//当查询到叶子节点或者该区间全部被毁灭或者该区间全都是完好的,返回这个区间的最大连续村庄数
return tr[id].tsum;
else//搜寻该位置的最大连续村庄数能延伸的最大区间
{
int mid=(tr[id].left+tr[id].right)/2;
if(pos<=mid)//延伸到左子树
{
if(pos>=(tr[id*2].right-tr[id*2].rsum+1))//左子树的村庄全都是完好的,那么还可以延续到右子树
return query(id*2,pos)+query(id*2+1,mid+1);
else
return query(id*2,pos);
}
else//同理,延伸到右子树
{
if(pos<=(tr[id*2+1].left+tr[id*2+1].lsum-1))//右子树的村庄全都是完好的,那么还可以延续到左子树
return query(id*2+1,pos)+query(id*2,mid);
else
return query(id*2+1,pos);
}
}
}
这个题也顺利解决了呢,不知道各位看懂了吗?当然自己实现的话还有许多需要注意的细节,特别是合并区间时的两种特殊情况,连续修复,连续毁灭等等,查询函数一定要延伸到最大不能再延伸的时候为止,这样才能得到最大连续村庄数哦!
时空复杂度如下所示:
Memory | Time | Length |
---|---|---|
4004(Kb) | 592(Ms) | 2089(Bytes) |
同样希望有大佬指点一下怎么进一步降低时空复杂度。我很期待哦(@ω@)!
下次也要加油!噢!
期待你们的评论!