[NOI2007]项链工厂(线段树)

【题解】

仔细探索题目性质:
1. "段数"具有类似结合律的性质,可以用线段树维护,断环为链,但线段树不易进行旋转、翻转操作 
2. 考虑用函数记录从初始到当前操作的变换方式,将对数列的变换转化为对询问的处理 
   发现:若函数 f(x)=k*x+b表示询问的位置x对应的初始位置,则旋转a之后,函数为:f(x)=k*(x-a)+b; 翻转之后为:f(x)=k*(n+2-x)+b 
3. 在纸上模拟了部分操作,发现无论旋转、翻转,1~n总连续出现 

由上述性质可知,由函数计算的询问区间的各点,对应的初始位置仍连续,所以可以用函数进行转换,且k总为正负1,可以用来记录当前环顺时针增大还是减小
因此:
实时维护函数,不变环而改变询问 
并用线段树的每个节点记录四个信息:setv:整个区间是否被整体赋值、lv/rv区间左/右端点的颜色、cntv区间内的段数 
用setv,lv,rv维护cntv即可 


【代码】

#include<stdio.h>
#include<stdlib.h>
int a[500005],setv[2000000],lv[2000000],rv[2000000],cntv[2000000];
int n,c,k=1,b=0;
void jh(int* a,int* b)
{
	int t=*a;
	*a=*b;
	*b=t;
}
int f(int x)//询问中的想、在线段树(原始数据)中的位置 
{
	return ((k*x+b)%n+n-1)%n+1;
}
void pushdown(int o)
{
	setv[o*2]=setv[o*2+1]=setv[o];
	setv[o]=-1;
}
void wh(int o)//根据当前的setv值维护其他信息 
{
	if(setv[o]!=-1)//包含叶结点 
	{
		lv[o]=rv[o]=setv[o];
		cntv[o]=1;
		return;
	}
	lv[o]=lv[o*2];
	rv[o]=rv[o*2+1];
	cntv[o]=cntv[o*2]+cntv[o*2+1];
	if(rv[o*2]==lv[o*2+1]) cntv[o]--;
}
void build(int o,int left,int right)
{
	int mid=(left+right)/2;
	if(left==right) setv[o]=a[left];
	else
	{
		setv[o]=-1;
		build(o*2,left,mid);
		build(o*2+1,mid+1,right);
	}
	wh(o);
}
void xg(int p,int x,int y,int o,int left,int right)
{
	int mid=(left+right)/2;
	if(x<=left&&right<=y) setv[o]=p;
	else//不包含叶结点 
	{
		if(setv[o]!=-1) pushdown(o);
		if(x<=mid) xg(p,x,y,o*2,left,mid);
		else wh(o*2);
		if(y>mid) xg(p,x,y,o*2+1,mid+1,right);
		else wh(o*2+1);
	}
	wh(o);
}
int cx(int x,int y,int o,int left,int right)
{
	int mid=(left+right)/2,ans=0;
	if(setv[o]!=-1) return 1;//注意 
	if(x<=left&&right<=y) return cntv[o];
	if(x<=mid) ans+=cx(x,y,o*2,left,mid);
	if(y>mid) ans+=cx(x,y,o*2+1,mid+1,right);
	if(x<=mid&&y>mid&&rv[o*2]==lv[o*2+1]) ans--;
	return ans;
}
int get(int x,int o,int left,int right)//得到线段树第x个叶结点的颜色 
{
	int mid=(left+right)/2;
	if(setv[o]!=-1) return setv[o];
	if(x<=mid) return get(x,o*2,left,mid);
	return get(x,o*2+1,mid+1,right);
}
int main()
{	
	char s[20];
	int i,Q,x,y,z,tx,ty,ans;
	scanf("%d%d",&n,&c);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build(1,1,n);
	scanf("%d",&Q);
	for(;Q>0;Q--)
	{
		scanf("%s",s);
		if(s[0]=='R')
		{
			scanf("%d",&x);
			b=(b-k*x)%n;
		}
		if(s[0]=='F')
		{
			b=(b+(n+2)*k)%n;
			k=-k;
		}
		if(s[0]=='S')
		{
			scanf("%d%d",&x,&y);
			x=f(x);
			y=f(y);
			tx=get(x,1,1,n);
			ty=get(y,1,1,n);
			xg(ty,x,x,1,1,n);
			xg(tx,y,y,1,1,n);
		}
		if(s[0]=='P')
		{
			scanf("%d%d%d",&x,&y,&z);
			x=f(x);
			y=f(y);
			if(k==-1) jh(&x,&y);
			if(x<=y) xg(z,x,y,1,1,n);
			else
			{
				xg(z,x,n,1,1,n);
				xg(z,1,y,1,1,n);
			}
		}
		if(s[0]=='C'&&s[1]!='S')
		{
			ans=cx(1,n,1,1,n);
			if(ans>1&&get(1,1,1,n)==get(n,1,1,n)) ans--;
			printf("%d\n",ans);
		}
		if(s[0]=='C'&&s[1]=='S')
		{
			scanf("%d%d",&x,&y);
			x=f(x);
			y=f(y);
			if(k==-1) jh(&x,&y);
			if(x<=y) ans=cx(x,y,1,1,n);
			else
			{
				ans=cx(x,n,1,1,n)+cx(1,y,1,1,n);
				if(get(1,1,1,n)==get(n,1,1,n)) ans--;
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}


你可能感兴趣的:(线段树,环,NOI)