POJ2892_Tunnel Warfare_solution

来源:互联网 发布:网络限速设置多少合适 编辑:程序博客网 时间:2024/05/29 03:18
Tunnel Warfare
Time Limit: 1000MS Memory Limit: 131072KTotal Submissions: 6355 Accepted: 2608

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 nextm lines describes an event.

There are three different events described in different format shown below:

  1. D x: The x-th village was destroyed.
  2. Q x: The Army commands requested the number of villages that x-th village was directly or indirectly connected with including itself.
  3. 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 9D 3D 6D 5Q 4Q 5RQ 4RQ 4

Sample Output

1024

Hint

An illustration of the sample input:

      OOOOOOOD 3   OOXOOOOD 6   OOXOOXOD 5   OOXOXXOR     OOXOOXOR     OOXOOOO

Source

POJ Monthly--2006.07.30, updog
虽然这题目分在线段树这类,想了两天都不知道怎么破,脑子迟钝就是没办法啊,最后看了下别人的解题思路,依然懵懵懂懂不知怎么下手。但是我知道这题目用C++有序集合来解的话,思路还是很简单的,我们把删除掉的元素都放在一个集合里,初始状态这个集合里先放上0和n+1这两个元素,这个集合是从小到大排序的,那么当我们要询问某个村庄有多少个村庄相连时,假设是编号为i 的村庄,只需把村庄i放入集合,那么在集合中恰好比i小的那个元素就是相连村庄的下界lower,恰好比i大的元素就是相连村庄的上界up,up-lower-1就是所求的答案。
举个例子,n=7的时候,先初始化有序集合set<int>={0,8}
删除3,那么把3加入set={0,3,8}
删除6,那么把6加入set={0,3,6,8}
删除5,那么把5加入set={0,3,5,6,8}
查询4,需要将4加入集合中,set={0,3,4,5,6,8}比4小的第一个元素为3,比4大的第一个元素为5,所以5-3-1=1就是答案。查询完将4从set删除
重建的时候只要从栈中取出要重建的村庄,再从集合中删除就可以了
比如接上面,重建,栈顶元素为5,在set中删除5,set={0,3,6,8}
查询4,恰好比4小的元素是3,恰好比4大的元素是6,6-3-1=2就是答案。
c++set集合是有序集合,而且增删查都是log(n)的复杂度,在JAVA中有个TreeSet也是一样的。下面贴上很丑的代码:
#include<stdio.h>#include<string.h>#include <set>using namespace std;int main (){int n,m,deletedStack[50005],stack_top;int i,p,lower,up;bool deleted[50005];//标记某个节点是否删除,已经删除的节点我们特殊处理,直接输出0char cmd[10];set<int>::iterator itlower,itup;//集合的迭代器,用来查上界和下界set<int> deletedPoint; //删除的点的有序集合while(scanf("%d%d",&n,&m)!=EOF){stack_top=0;//初始被删除的元素为0个,栈为空memset(deleted,0,sizeof(deleted));//没有元素被删除deletedPoint.insert(0);//集合中开始加入0,n+1两个元素deletedPoint.insert(n+1);for(i=1;i<=m;i++){scanf("%s",&cmd);if(cmd[0]=='D')//摧毁某个村庄{scanf("%d",&p);deletedStack[++stack_top]=p;//入栈deletedPoint.insert(p);//加入被删除元素的有序集合deleted[p]=true;//被删除}else if(cmd[0]=='R')//重建栈顶村庄{p=deletedStack[stack_top--];//出栈deletedPoint.erase(p);//从集合删除deleted[p]=false;//恢复}else //询问{scanf("%d",&p);if(deleted[p])//被删除的村庄直接输出0{printf("0\n");continue;}deletedPoint.insert(p);//先将要查询的村庄加入有序集合,因为这样才能查询itlower=itup=deletedPoint.find(p);//再记录下要查询的村庄所在位置的迭代器itlower--;//下一位就是恰好比它小的被摧毁村庄,即相连村庄的下界lower=*itlower;itup++;//往上一位就是恰好比它大的被摧毁的村庄,即相连村庄的上界up=*itup;deletedPoint.erase(p);//查询完毕,把查询的村庄删除printf("%d\n",up-1-lower);}}deletedPoint.clear();//清空集合,以备下一个testCase使用}return 0;}

下面再来说说怎么使用线段树解这个题目,其实我们查询某个点的时候就是查询这个点所在的最大连续区间,我们要记录的就是一些连续区间的起点和终点。但是如果直接去记录起点和终点,那也不好操作,比如一个区间很多被删除的点。我们把一个区间一分为二,并且记录以左端点开始的最大相连村庄长度,以及以右端点结束的最大长度。上个图来说明一下:
当我们把一个区间分成两段的时候,左孩子记录这些信息:以Left开始的最大长度,以mid结束的最大长度;右孩子记录这些信息:以mid+1开始的最大长度,以Right结束的最大长度。如果我们要查询编号为destination的村庄,只要看它落在哪个区间。
 定义一个查询函数query(left,right,destination)
if      destination<=mid:
        if destination落在mid左边的黄色部分
              答案是mid左边的黄色部分+mid右边的蓝色部分
             return query(left,mid,destination)+query(mid+1,right,mid+1)//注意这里在查询右子树的时候是以mid+1为目标
         else
             //这时答案一定在left右边黄色部分+白色部分,这时和右子树就没有关系了,缩小了一半查询范围
            return   query(left,mid,destination);
else   //就是destination>=mid+1
         if   destination落在mid右边的蓝色部分
             答案是mid右边的蓝色部分+mid左边的黄色部分
             return query(left,mid,mid)+query(mid+1,right,destination);//注意这里在查询左子树的是以mid为目标
         else
            //这时答案就在Right左边的蓝色部分和白色部分,这时和左子树就没有关系了,缩小了一半查询范围
            return query(mid+1,right,destination);
那查询的终止条件是什么呢?我们在线段树中加入一个域max_len记录该段区间最大的连续区间,有以下三个条件之一就可以返回:
 1、遇到了叶子节点
2、max_len=0,表示这段区域所有村庄都被摧毁//为什么要max_len这个信息域的原因正在于此,因为当start_left_len=end_right_len=0的时候,max_len可以不为0
3、max_len=Right-Left+1,表示这段区域是一个完全相连的区间
最后我们获得的就是destination所在那段区间的最大长度。
查询的问题我们解决了,可是怎么更新呢?
先对上面说的问题加些变量来表示,比如以Left开始的最大长度start_left_len,以Right结束的最大长度end_right_len。因为更新的时候只对一个点更新,我们先直接找到要更新的节点,令start_left_len=end_right_len=max_len=0,然后再往上回溯去更新父节点。父节点的信息这样更新:
父节点start_left_len=左子结点start_left_len;父节点end_right_len=右子结点end_right_len;父节点max_len=max(左子结点max_len,右子结点max_len,左子结点end_right_len+右子结点start_left_len);If 左子结点是完全相连的区间,即max_len=right-left+1      父节点start_left_len+=右子结点start_left_len;If  右子结点是完全相连的区间,即max_len=right-left+1      父节点end_right_len+=左子结点end_right_len;

对照上面画的图,自己在画画图就很清楚了。
附上代码:
#include<stdio.h>#include<string.h>#define MAXN 50005int n,m,stack[MAXN],stack_top;//stack用来存储删除的节点,stack_top=0时stack为空struct segTreeInfo//线段树结构体{int left,right;//管理区间int start_left_len;//以left开始的相连节点个数int end_right_len;//以right结束的相连节点个数int max_len;//left至right整个区间出现的最长相连节点个数};segTreeInfo segTree[3*MAXN];int getMax(int a,int b){return a>b?a:b;}void buildTree(int pos,int left,int right){int len=right-left+1;segTree[pos].left=left;segTree[pos].right=right;segTree[pos].start_left_len=len;//初始状态长度是整个管理区间segTree[pos].end_right_len=len;segTree[pos].max_len=len;if(left==right){return;}int mid=(left+right)/2;buildTree(pos<<1,left,mid);buildTree(pos<<1|1,mid+1,right);}void update(int pos,int destination,int operation)//destination表示要更新的节点,operation=1表示rebuild,operation=0表示destroy{int left=segTree[pos].left;int right=segTree[pos].right;if(left==right&&destination==left)//找到叶子节点才开始更新{segTree[pos].start_left_len=operation;//rebuild变为1,然后开始往上回溯更新父节点segTree[pos].end_right_len=operation;//destroy变为0,然后开始往上回溯更新父节点segTree[pos].max_len=operation;return;}int mid=(left+right)/2;if(destination<=mid)update(pos<<1,destination,operation);elseupdate(pos<<1|1,destination,operation);segTree[pos].start_left_len=segTree[pos<<1].start_left_len;//以left开始的长度=左孩子以left开始的长度segTree[pos].end_right_len=segTree[pos<<1|1].end_right_len;//以right结束的长度=右孩子以right结束的长度segTree[pos].max_len=getMax(segTree[pos<<1].end_right_len+segTree[pos<<1|1].start_left_len,getMax(segTree[pos<<1|1].max_len,segTree[pos<<1].max_len));/*上面有点长的这条语句好混乱,就是从三个数里取最大值,首先是max_len=getMax(左孩子能形成的最大长度,右孩子能形成的最大长度)因为mid和mid+1这两个节点可能是相连的,所以这个长度就是(左孩子以mid结尾的长度+右孩子以mid+1开始的长度),用这个长度再去更新max_len就是最终的max_len*/if(segTree[pos<<1].start_left_len==mid-left+1)//左孩子是完全相连的串,即没有被摧毁的村庄{segTree[pos].start_left_len+=segTree[pos<<1|1].start_left_len;//那么父节点以left开始的长度需要加上右孩子最左段长度}if(segTree[pos<<1|1].end_right_len==right-(mid+1)+1)//右孩子是完全相连的串{segTree[pos].end_right_len+=segTree[pos<<1].end_right_len;//那么父节点以right结尾的长度需要加上左孩子最右段的长度}}int query(int pos,int destination)//查询与destination相连的村庄数量{int left=segTree[pos].left,right=segTree[pos].right;/*在这些情况下返回:到达叶子节点;到达的这段区间所有村庄被摧毁;到达的这段区间所有村庄都相连*/if(left==right||segTree[pos].max_len==0||segTree[pos].max_len==right-left+1)return segTree[pos].max_len;/*这里处理的情况即是这段区间有被摧毁的村庄,这段区间包含要查找的目标destination*/int mid=(left+right)/2;if(destination<=mid)//destination落在左孩子管理区间{if(destination>=mid-segTree[pos<<1].end_right_len+1)//如果destination落在左孩子管理的最右段return query(pos<<1,destination)+query(pos<<1|1,mid+1);//那么与destination相连的村庄长度还要加上右孩子的最左段else//不落在左孩子管理的最右段,那么与destination相连的长度只和左孩子管理的区间相关return query(pos<<1,destination);}else//destination落在右孩子管理区间{if(destination<=mid+segTree[pos<<1|1].start_left_len)//如果destination落在右孩子的最左段return query(pos<<1,mid)+query(pos<<1|1,destination);//那么与destination相连的村庄长度还要加上左孩子的最右段else//不落在右孩子的最左段,那么与destination相连的长度只和右孩子管理的区间相关return query(pos<<1|1,destination);}}int main(){int i,destination;char cmd[5];while(scanf("%d%d",&n,&m)!=EOF){//n=村庄数量,m=操作命令数量stack_top=0;//初始时没摧毁任何村庄,栈为空buildTree(1,1,n);//建立管理n个村庄的线段树for(i=1;i<=m;i++)//顺序处理m个命令{scanf("%s",&cmd);if(cmd[0]=='D')//摧毁村庄{scanf("%d",&destination);stack[++stack_top]=destination;//被摧毁的村庄入栈update(1,destination,0);//摧毁destination,更新线段树,后面的0表示摧毁}else if(cmd[0]=='R')//重建村庄{destination=stack[stack_top--];//从栈中取出一个村庄进行重建update(1,destination,1);//重建destination,更新线段树,后面的1表示重建}else//询问与destination相连的村庄数量{scanf("%d",&destination);printf("%d\n",query(1,destination));}}}return 0;}


 

原创粉丝点击