洛谷P3797 妖梦斩木棒

题目背景

妖梦是住在白玉楼的半人半灵,拥有使用剑术程度的能力。

题目描述

有一天,妖梦正在练习剑术。地面上摆放了一支非常长的木棒,妖梦把它们切成了等长的n段。现在这个木棒可以看做由三种小段构成,中间的n-2段都是左右都被切断的断头,我们记做’X’,最左边的一段和最右边的一段各有一个圆头,记做’(‘和’)’。幽幽子吃饱后闲来无事,决定戏弄一下妖梦。她拿来了许多这样的三种小段木棒,来替换掉妖梦切下来的n段中的一部分,然后问妖梦一些问题。这些操作可以这样描述:

1 x C 将第x个小段的木棒替换成C型,C只会是’X’,’(‘,’)’中的一种

2 l r 询问妖梦从第l段到第r段之间(含l,r),有多少个完整的木棒

完整的木棒左右两端必须分别为’(‘和’)’,并且中间要么什么都没有,要么只能有’X’。

虽然妖梦能够数清楚这些问题,但幽幽子觉得她回答得太慢了,你能教给妖梦一个更快的办法吗?

输入格式

第一行两个整数n,m,n表示共有n段木棒,m表示有m次操作。

木棒的初始形状为(XXXXXX......XXXXXX)。

接下来m行,每行三个整数/字符,用空格隔开。第一个整数为1或2,表示操作的类型,若类型为1,则接下来一个整数x,一个字符C。若类型为2,接下来两个整数l,r。含义见题目描述。

输出格式

对于每一个操作2,输出一行一个整数,表示对应询问的答案。

输入输出样例

输入 #1复制

4 4
2 1 4
2 2 4
1 2 (
2 2 4

输出 #1复制

1
0
1

说明/提示

对于30%的数据,1<=n,m<=1000

对于100%的数据,1<=n,m<=200000

by-orangebird

上代码:

#include<cstdio>
#include<cstdlib>
#include<ctype.h>
#define ls (now<<1)
#define rs ((now<<1)+1)
const int MARX = 2e5+10;
//=============================================================
struct node
{
	int L,R;//维护的区间,
	bool allX,side[3];//是否全为X , side[0]:最左侧是否有) , side[1]:最右侧是否有( ,
	int sum,pos[3]; //区间内完整木棒数, 及区间内 最左侧),最右侧( 位置 
}tree[MARX<<4];
int n,m, map[110],map1[3];//map存映射关系, 0<->( , 1<->) , 2<->X 
char now_list[MARX];//当前的 字串 
//=============================================================
inline int read()
{
    int s=1, w=0; char ch=getchar();
    for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
    for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
    return s*w;
}
void Build(int now,int l,int r)//常规建树
{
	tree[now].L=l,tree[now].R=r;
	if(l == r) return ;
	int mid = (l+r)>>1;
	Build(ls,l,mid);
	Build(rs,mid+1,r);
}
void pushup(int now)//更新 第now个节点 的各信息 
{
	//维护 区间最左侧 是否有) 
	if(tree[ls].side[0]) tree[now].side[0]=1, tree[now].pos[0] = tree[ls].pos[0]; 
	else if(!tree[ls].allX && tree[rs].side[0]) tree[now].side[0]=1 , tree[now].pos[0]= tree[rs].pos[0];
	else tree[now].side[0] = 0;
	//维护 区间最右侧 是否有( 
	if(tree[rs].side[1]) tree[now].side[1]=1 , tree[now].pos[1] = tree[rs].pos[1];
	else if(!tree[rs].allX && tree[ls].side[1]) tree[now].side[1]=1 , tree[now].pos[1]= tree[ls].pos[1];
	else tree[now].side[1] = 0;
	//更新区间 合法字串数, 区间是否全为'X' 
	tree[now].sum = tree[ls].sum+tree[rs].sum + (tree[ls].side[1] && tree[rs].side[0]);;
	tree[now].allX = (tree[ls].allX || tree[rs].allX);
}
void Change(int now,int pos,int value) //单点修改, 将第pos个位置修改为 value 
{
	if(tree[now].L==pos && tree[now].R==pos)//当前区间 即指定位置 
	{
	  now_list[pos] = map1[value];//更新各信息 
	  tree[now].allX = (value!=2);//是否全为X 
	  tree[now].side[0] = tree[now].side[1] = 0;//更新 最左,最右侧元素 
	  tree[now].side[value] = 1, tree[now].pos[value]=pos;
	  return ;
	}
	int mid = (tree[now].L+tree[now].R)>>1;
	if(pos <= mid) Change(ls,pos,value);
	if(pos > mid) Change(rs,pos,value);
	pushup(now);//更新当前位置 
}
int Inquiry(int now,int L,int R)//查询区间 L,R 的目标字串数 
{
	if(L <= tree[now].L && tree[now].R <= R) return tree[now].sum;
	int sum=0,flag=0;//flag 判断区间 是否同时被左右子区间更新过   
	int mid = (tree[now].L+tree[now].R)>>1;
	if(L<=mid) sum += Inquiry(ls,L,R), flag++;
	if(R>mid) sum += Inquiry(rs,L,R), flag++;
	//存在 合法字串 横跨左右子区间  
	if(flag == 2) sum += (tree[ls].side[1] && L<=tree[ls].pos[1] && tree[rs].side[0] && R>=tree[rs].pos[0]);
	return sum; 
}
void prepare()//预处理 
{
	n=read() , m=read();
	Build(1,1,n);
	Change(1,1,1) , Change(1,n,0);
	
	now_list[1]='(' , now_list[n]=')';//原始字串 
	for(int i=2; i<n; i++) now_list[i]='X';
	
	map['(']=1, map[')']=0, map['X']=2;//建立映射关系 
	map1[1]='(',map1[0]=')',map1[2]='X';
}
//=============================================================
signed main()
{
	prepare();
	for(int i=1; i<=m; i++)
	{
	  int type=read(),value1,value2;
	  char value3;
	  if(type == 1) //修改操作 
	  {
	  	value1=read(); value3 = getchar();//读入防卡 
	  	getchar();
	  	if(now_list[value1] == value3) continue;
	  	Change(1,value1,map[(int)(value3)]);
	  }
	  else //查询操作 
	  {
	  	value1=read(), value2=read();
		printf("%d\n",Inquiry(1,value1,value2));
	  }
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值