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
题意:有n个村庄m次操作:摧毁村庄、修复最近摧毁的村庄、询问包含村庄在内的最长连续子列长度。首先我们可以想到可以用到栈这个后进先出数据结构来存储被摧毁的村庄。然后就是一波线段树来袭。由于成员比较多,我在这里用用结构体来存储信息(这也是我第一次用结构体玩线段树~)存储每个区间从最左端最多能连续几个,从最右端能连续几个,最长的连续子序列的长度。为了方便可以置内置函数Mid和Len:区间中点和区间长度。初始建树时每个叶子节点的lsum,rsum,sum都为1,其他由向上更新得到相应的值。那么怎么向上更新呢?我们需要维护3个成员即lsum,rsum,sum。那么这时你应该拿出来一张草稿纸对着样例做一下模拟了,首先父节点的lsum初始化为左孩子的lsum(这是最低的要求,也是肯定的),如果左孩子的lsum等于区间长度则表明父节点的lsum还可以做到右孩子的lsum上去(具体情况看代码)父节点的rsum同理。还有父节点的sum可以取父节点的lsum,rsum和(左孩子的rsum加上右孩子的lsum)的最大值。画个图秒懂,好好想想~。接下来就是摧毁和修复操作了,摧毁代表值为0,而修复代表值为1,这也就是为什么建线段树时为什么初始化为1的原因!如果是摧毁,那么找到相应叶子节点将其3个成员全部赋值为0即可,修复同理全部赋值为1.还有最后的一步也是最关键的一步:查询!首先我们考虑到特殊情况,如果要查询的节点x在某个区间的最左侧或者最右侧则可O(1)得到,因为我们存的就是lsum,rsum。还有中间的也可O(1)得到,即为左儿子的rsum加上右儿子的lsum。其他情况只能找左右区间递归下去了~~。
#include<iostream>
#include<cstring>
#include<stack>
#define mem(a,x) memset(a,x,sizeof(a));
using namespace std;
const int maxn=50050;
char str[10];
int n,m,x;
struct node
{
int lsum,rsum,sum;//最左侧能到达右边几个,最右侧能到达左边几个,最大连续字段
int l,r;
int Mid()
{
return (l+r)/2; //内置函数为了方便
}
int Len()
{
return r-l+1;
}
} a[maxn<<2];
void pushup(int rt)//往上更新
{
a[rt].lsum=a[rt*2].lsum;//初始化赋值
a[rt].rsum=a[rt*2+1].rsum;
if(a[rt*2].lsum==a[rt*2].Len()) //特殊情况判断
a[rt].lsum=a[rt*2].lsum+a[rt*2+1].lsum;
if(a[rt*2+1].rsum==a[rt*2+1].Len())//特殊情况判断
a[rt].rsum=a[rt*2+1].rsum+a[rt*2].rsum;
a[rt].sum=max(a[rt*2].rsum+a[rt*2+1].lsum,max(a[rt*2].lsum,a[rt*2+1].rsum));//取最大连续子段
}
void build(int l,int r,int rt)//建立线段树
{
a[rt].l=l;
a[rt].r=r;
a[rt].lsum=a[rt].rsum=a[rt].sum=a[rt].Len();//这里子节点全部为1,其他的由向上更新得到
if(l==r)
return ;
int mid=(l+r)/2;
build(l,mid,rt*2);
build(mid+1,r,rt*2+1);
pushup(rt);
}
void update(int x,int w,int rt)//将x节点得值改为w
{
if(a[rt].l==a[rt].r)
{
a[rt].lsum=a[rt].rsum=a[rt].sum=w;
return ;
}
if(x<=a[rt].Mid())
update(x,w,rt*2);
else
update(x,w,rt*2+1);
pushup(rt);
}
int querysum(int x,int rt)
{
if(a[rt].sum==0)
return a[rt].sum;
if(x<a[rt].l+a[rt].lsum)//左侧
return a[rt].lsum;
if(x>a[rt].r-a[rt].rsum)//右侧
return a[rt].rsum;
if(x>a[rt*2].r-a[rt*2].rsum&&x<a[rt*2+1].l+a[rt*2+1].lsum)//中间,注意理解,画图!!
return a[rt*2].rsum+a[rt*2+1].lsum;
if(x<=a[rt].Mid()) //如果没有在上面的特殊情况里找到结果,则查询子区间
return querysum(x,rt*2);
else
return querysum(x,rt*2+1);
return 0; //可有可无
}
int main()
{
while(cin>>n>>m)
{
stack<int>S;//用后进先出的栈来实现重建最近破坏的村庄
build(1,n,1);
for(int i=1; i<=m; i++)
{
cin>>str;
if(str[0]=='D')//破坏1个村庄
{
cin>>x;
update(x,0,1);//在x节点上变为0
S.push(x);
}
else if(str[0]=='R'&&S.size())//重建最近被破坏的一个,如果没有则跳过
{
update(S.top(),1,1);
S.pop();
}
else if(str[0]=='Q')//询问包含x在内的最长连续区间
{
cin>>x;
cout<<querysum(x,1)<<endl;
}
}
}
return 0;
}
本文介绍了一个基于抗日战争时期地道战的模拟系统,使用线段树和栈数据结构处理村庄连接状态的变化,包括摧毁、重建和查询最长连续子列。
1174

被折叠的 条评论
为什么被折叠?



