FZU-2105 Digits Count (两种标记成段更新)

题目大意:给n个0~15之间的数,有3种更新操作,1种询问操作。3种更新操作是:1、让某个闭区间的所有数字与一个0~15之间的数字进行逻辑与运算;2、让某个闭区间的所有数字与一个0~15之间的数字进行逻辑或运算;3、让某个闭区间的所有数字与一个0~15之间的数字进行异或运算。一种询问操作是询问某个闭区间的所有数字之和。

题目分析:所有的输入数字都是在0~15之间,可以二进制中的每一位建立线段树,建立四棵。3种更新操作实际上只有两种,即区间置位和区间反转。用两个懒惰标记,当进行区间置位更新的时候,要清除已经存在的反转标记,这样就能保证标记下传时两种标记下传的先后次序。

代码如下:

# include<iostream>
# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;

# define LL long long

const int N=1000000;

int n,m;
int a[N+5];
int tr[4][N*4+5];
int lazy[4][N*4+5];
int lazy_xor[4][N*4+5];
char com[5];

void pushDown(int id,int rt,int l,int r)
{
	int mid=l+(r-l)/2;
	if(lazy[id][rt]!=-1){
		lazy_xor[id][rt<<1]=lazy_xor[id][rt<<1|1]=0;
		lazy[id][rt<<1]=lazy[id][rt<<1|1]=lazy[id][rt];
		tr[id][rt<<1]=lazy[id][rt]*(mid-l+1);
		tr[id][rt<<1|1]=lazy[id][rt]*(r-mid);
		lazy[id][rt]=-1;
	}
	int &q=lazy_xor[id][rt];
	if(q==1){
		lazy_xor[id][rt<<1]^=1;
		lazy_xor[id][rt<<1|1]^=1;
		tr[id][rt<<1]=mid-l+1-tr[id][rt<<1];
		tr[id][rt<<1|1]=r-mid-tr[id][rt<<1|1];
		q=0;
	}
}

void pushUp(int id,int rt)
{
	tr[id][rt]=tr[id][rt<<1]+tr[id][rt<<1|1];
}

void build(int id,int rt,int l,int r)
{
	lazy[id][rt]=-1;
	lazy_xor[id][rt]=0;
	if(l==r){
		if((a[r]&(1<<id))>0) tr[id][rt]=1;
		else tr[id][rt]=0;
	}else{
		int mid=l+(r-l)/2;
		build(id,rt<<1,l,mid);
		build(id,rt<<1|1,mid+1,r);
		pushUp(id,rt);
	}
}

void update(int id,int rt,int l,int r,int L,int R,int x)
{
	if(L<=l&&r<=R){
		if(x==2){
			if(lazy[id][rt]!=-1) lazy[id][rt]^=1;
			else lazy_xor[id][rt]^=1;
			tr[id][rt]=(r-l+1)-tr[id][rt];
		}else{
			lazy[id][rt]=x;
			lazy_xor[id][rt]=0;
			tr[id][rt]=x*(r-l+1);
		}
	}else{
		pushDown(id,rt,l,r);
		int mid=l+(r-l)/2;
		if(L<=mid) update(id,rt<<1,l,mid,L,R,x);
		if(R>mid) update(id,rt<<1|1,mid+1,r,L,R,x);
		pushUp(id,rt);
	}
}

LL query(int id,int rt,int l,int r,int L,int R)
{
	if(L<=l&&r<=R) return tr[id][rt];
	pushDown(id,rt,l,r);
	int mid=l+(r-l)/2;
	LL res=0;
	if(L<=mid) res+=query(id,rt<<1,l,mid,L,R);
	if(R>mid) res+=query(id,rt<<1|1,mid+1,r,L,R);
	return res;
}

int main()
{
	//freopen("in.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;++i) scanf("%d",a+i);
		for(int i=0;i<4;++i)
			build(i,1,0,n-1);
		int o,b,c;
		while(m--)
		{
			scanf("%s",com);
			if(com[0]==‘S‘){
				scanf("%d%d",&b,&c);
				int ans=0;
				for(int i=0;i<4;++i){
					ans+=query(i,1,0,n-1,b,c)*(1<<i);
				}
				printf("%d\n",ans);
			}else{
				scanf("%d%d%d",&o,&b,&c);
				if(com[0]==‘X‘){
					for(int i=0;i<4;++i) if(o&(1<<i))
						update(i,1,0,n-1,b,c,2);
				}else if(com[0]==‘O‘){
					for(int i=0;i<4;++i) if(o&(1<<i))
						update(i,1,0,n-1,b,c,1);
				}else if(com[0]==‘A‘){
					for(int i=0;i<4;++i) if(!(o&(1<<i)))
						update(i,1,0,n-1,b,c,0);
				}
			}
		}
	}
	return 0;
}

  

时间: 2024-10-11 00:40:57

FZU-2105 Digits Count (两种标记成段更新)的相关文章

ACM: FZU 2105 Digits Count - 位运算的线段树【黑科技福利】

FZU 2105  Digits Count Time Limit:10000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Practice Description Given N integers A={A[0],A[1],...,A[N-1]}. Here we have some operations: Operation 1: AND opn L R Here opn, L and R are integer

POJ 2777 Count Color (线段树成段更新+二进制思维)

题目链接:http://poj.org/problem?id=2777 题意是有L个单位长的画板,T种颜色,O个操作.画板初始化为颜色1.操作C讲l到r单位之间的颜色变为c,操作P查询l到r单位之间的颜色有几种. 很明显的线段树成段更新,但是查询却不好弄.经过提醒,发现颜色的种类最多不超过30种,所以我们用二进制的思维解决这个问题,颜色1可以用二进制的1表示,同理,颜色2用二进制的10表示,3用100,....假设有一个区间有颜色2和颜色3,那么区间的值为二进制的110(十进制为6).那我们就把

fzu 2105 Digits Count 线段树

题目链接:http://acm.fzu.edu.cn/problem.php?pid=2105 题意: 给出一个数组A[0]-A[n-1],每个数最大是16.有4种操作: AND opn L R:L-R区间内的数都AND上opn这个数 OR opn L R:L-R区间内的数都OR上opn这个数 XOR opn L R:L-R区间内的数都XOR上opn这个数 SUM L R:求L-R区间内所有数的和. 0 <= opn <= 16 思路: 可以发现A[i]和opn最大都不超过16,所以可以设4个

FZU 2105 Digits Count(按位维护线段树)

[题目链接] http://acm.fzu.edu.cn/problem.php?pid=2105 [题目大意] 给出一个序列,数字均小于16,为正数,每次区间操作可以使得 1. [l,r]区间and一个数 2. [l,r]区间or一个数 3. [l,r]区间xor一个数 4. [l,r]区间查询和 操作数均为小于16的非负整数 [题解] 由于操作数很小,因此我们可以按位维护四棵线段树,表示二进制中的第i位, 对于and操作,只有当and的当前位为0时才对区间有影响,效果是将区间全部变为0, 对

FZU 2105 Digits Count(位数计算)

Description 题目描述 Given N integers A={A[0],A[1],...,A[N-1]}. Here we have some operations: Operation 1: AND opn L R Here opn, L and R are integers. For L≤i≤R, we do A[i]=A[i] AND opn (here "AND" is bitwise operation). Operation 2: OR opn L R Here

FOJ 2105 Digits Count

题意:对一串数字进行抑或某数,和某数,或某数,统计某区间和的操作. 思路:因为化成二进制就4位可以建4颗线段树,每颗代表一位二进制. and 如果该为是1  直接无视,是0则成段赋值为0: or  如果是0 无视,是1则成段赋值为1: xor 成段亦或,1个数和0个数交换: sum 求和: #include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include <

mysql级联更新的两种方式:触发器更新和外键

1.mysql级联更新有两种方式:触发器更新和外键更新. 2.触发器更新和外键更新的目的都是为了保证数据完整性. 我们通常有这样的需求:删除表Table 1中记录,需要同时删除其它表中与Table 1有关的若干记录. 举个例子: 现有2个实体- 麻将机 学生.课程,1种联系- 成绩 分别创建 学生表 students, 课程表course,成绩表score --创建 学生表 students CREATE TABLE IF NOT EXISTS `students` ( `id` int(11)

Count the Colors(线段树之区间成段更新)

萌萌哒的传送门 这道题目是线段树区间成段更新的应用,我们只需在建立线段树时从原来的左右儿子不相连,改为相连即可以解决此类问题. 如从原来的[l,mid] , [mid + 1,r] 改为 [l,mid],[mid,r]即可; /********************* * zoj1610 * * 线段树的区间成段更新 * 延迟标记 * *********************/ #include <iostream> #include <cstring> #include &l

【POJ】3468 A Simple Problem with Integers ——线段树 成段更新 懒惰标记

A Simple Problem with Integers Time Limit:5000MS   Memory Limit:131072K Case Time Limit:2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each