线段树

基础知识

1、 线段树是二叉树,且必定是平衡二叉树,但不一定是完全二叉树。
2、 对于区间[ L , R ],令mid=(L+R)/2,则其左子树为[L,mid],右子树为[mid+1,R],当L==R时,该区间为线段树的叶子,无需继续往下划分。
3、 线段树虽然不是完全二叉树,但是可以用完全二叉树的方式去构造并存储它,只是最后一层可能存在某些叶子与叶子之间出现“空叶子”,这个无需理会,同样给空叶子按顺序编号,在遍历线段树时当判断到a==b时就认为到了叶子,“空叶子”永远也不会遍历到。
4、 之所以要用完全二叉树的方式去存储线段树,是为了提高在插入线段和搜索时的效率。用p2,p2+1的索引方式检索p的左右子树要比指针快得多。
5、线段树的精髓是,能不往下搜索,就不要往下搜索,尽可能利用子树的根的信息去获取整棵子树的信息。如果在插入线段或检索特征值时,每次都非要搜索到叶子,还不如直接建一棵普通树更来得方便
6、对一维线段树来说,每次更新以及查询的时间复杂度为 O ( l o g n ) O(logn) O(logn)。常用的解决RMQ问题有ST算法,二者预处理时间都是 O ( n l o g n ) O(nlogn) O(nlogn)

总结
1、线段树是平衡二叉树,并以完全二叉树的方式构造,但并不一定是完全二叉树。有些空的叶节点永远都遍历不到
2、尽量不要遍历要叶节点,即L==R,Update和Query的时候,尽量更新到根节点,然后配合lazy数组来使用。但有时候很难只更新到区间,用lazy很难表示区间和的累积。此时只能做单点更新
3、用线段树解题时,先想明白,用线段树的叶节点来维护什么,用根节点来维护什么
4、单点更新表示L==R,区间更新利用(l<=L&&R<=r)+ lazy数组维护。如果要走到叶节点就L==R,如果只走到某个区间就(l<=L&&R<=r)即可。
5、线段树给的区间大小,确定叶节点所维护的大小,4倍即可

(1)Build函数

  • 区间left、right、lazy赋值
  • 递归出口,如果走到叶节点,赋值,return
  • 递归调用自己的左右结点
  • 回溯Push_up:用叶节点的值更新父节点

(2)Update函数

  • 递归出口:if(l<=L&&R<=r)说明已经覆盖了某区间,做更新操作
  • Push_down:用lazy更新
  • if(l<=mid)
  • if(r>mid)
  • 回溯Push_up
    (3)Query函数
    1、递归出口:if(l<=L&&R<=r)说明已经覆盖了某区间,做查询操作
    2、Push_down:用lazy更新
    3、计算答案并返回
if(l<=mid)
	ans+=Query(ls,l,r,c);
if(r>mid)
	ans+=Query(rs,l,r,c);
return ans;

题解

1、敌兵布阵 HDU - 1166
类型:单点更新,区间查询
题意:三种操作Query l r 输出区间l–>r的和。add x y,对x点加y,以及sub x y对x点减y
思路:用叶节点维护每一个营地的人数,用根节点维护区间的总人数
更新到叶节点,查询到所包含区间

const int maxn=5e4+10,INF=0x3f3f3f3f;
const int mod=1e9+7;
int ST[maxn<<2];
void Push_up(int rt)
{
	ST[rt]=ST[ls]+ST[rs];
}

void Build(int rt,int l,int r)
{
	if(l==r)
	{
		scanf("%d",&ST[rt]);
		return;
	}
	int mid=(l+r)>>1;
	Build(ls,l,mid);
	Build(rs,mid+1,r);
	Push_up(rt);
}
void Update(int rt,int p,int l,int r,int c)
{
	if(l==r)
	{
		ST[rt]+=c;
		return;
	}
	int mid=(l+r)>>1;
	if(p<=mid)
		Update(ls,p,l,mid,c);
	if(p>mid)
		Update(rs,p,mid+1,r,c);
	Push_up(rt);
}
int Query(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt];
	int mid=(L+R)>>1;
	int ans=0;
	if(l<=mid)
		ans+=Query(ls,l,r,L,mid);
	if(r>mid)
		ans+=Query(rs,l,r,mid+1,R);
	return ans;
}


int main()
{
	int T;
	scanf("%d",&T);
	int Cas=0;
	while(T--)
	{		
		int n;
		scanf("%d",&n);
		Build(1,1,n);
		char op[10];
		
		printf("Case %d:\n",++Cas); 
		while(~scanf("%s",&op))
		{
			if(op[0]=='Q')
			{
				int l,r;
				scanf("%d %d",&l,&r);
				printf("%d\n",Query(1,l,r,1,n));
			}
			else if(op[0]=='A')
			{
				int p,c;
				scanf("%d %d",&p,&c);
				Update(1,p,1,n,c);
			}
			else if(op[0]=='E')
				break;	
			else if(op[0]=='S') 
			{
				int p,c;
				scanf("%d %d",&p,&c);
				Update(1,p,1,n,-c);
			}		
		}
	}
	return 0;
}

2、Can you answer these queries? HDU - 4027
类型:区间更新(无法对区间直接更新,只能做单点更新),区间查询
题意:两种操作
0 a b 代表将区间[a,b]内的数值,变为自己的平方根
1 a b 代表查询区间[a,b]内总值
思路:叶节点维护初始战斗力,根节点维护区间和。对区间中的每个点做开方的更新操作,虽然是区间更新,但是很难直接累加到lazy数组上。因此还是要做单点跟新
单点更新时,只需要明白一件事,就是一个最大的数,最多开6、7次平方,就会变成1。因此当一个区间的的数值ST[ rt ] == R-L+1就说明它的叶子节点都已经是1了,剪枝即可

const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M;
ll ST[maxn<<2];int T,X,Y; 
void Push_up(int rt)
{
	ST[rt]=ST[ls]+ST[rs];
}
void Build(int rt,int L,int R)
{
	if(L==R)
	{
		scanf("%lld",&ST[rt]);
		return;
	}
	int mid=(L+R)>>1;
	Build(ls,L,mid);
	Build(rs,mid+1,R);
	Push_up(rt);
}
void Update(int rt,int l,int r,int L,int R)
{
	if((R-L+1)==ST[rt])
		return;
	if(L==R)
	{
		ST[rt]=sqrt(ST[rt]);
		return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid);
	if(r>mid)
		Update(rs,l,r,mid+1,R);
	Push_up(rt);
}

ll Query(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
	{
		return ST[rt];
	}
	int mid=(L+R)>>1;
	ll ans=0;
	if(l<=mid)
		ans+=Query(ls,l,r,L,mid);
	if(r>mid)
		ans+=Query(rs,l,r,mid+1,R);
	return ans;
}
int main()
{
	int Cas=0;
	while(~scanf("%d",&N))
   	{
    	printf("Case #%d:\n",++Cas);
    	Build(1,1,N);
    	scanf("%d",&M);
    	while(M--)
    	{
    		scanf("%d %d %d",&T,&X,&Y);
    		if(X>Y)
    			swap(X,Y);
    		if(T==0)
    		{
    			Update(1,X,Y,1,N);
    		}
    		else if(T==1)
    		{
    			printf("%lld\n",Query(1,X,Y,1,N));
    		}
    	}
    	printf("\n");
    	}
	return 0;
}

B - I Hate It HDU - 1754
类型:单点更新,区间查询
题意:两种操作Q l r:输出区间l–>r的最高成绩。U x y:将学号x的学生的成绩更新为y
思路:叶节点维护每一个学生的成绩,根节点维护区间的最高成绩
更新到叶节点(L==R),查询到所包含区间(l<=L&&R<=r)

const int maxn=2e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M,x,y;int ST[maxn<<2];
char op[10];
void Push_up(int rt)
{
	ST[rt]=max(ST[ls],ST[rs]);
}
void Build(int rt,int L,int R)
{
	if(L==R)
	{
		scanf("%d",&ST[rt]);
		return;
	}
	int mid=(L+R)>>1;
	Build(ls,L,mid);
	Build(rs,mid+1,R);
	Push_up(rt);
}
void Update(int rt,int p,int L,int R,int c)
{
	if(L==R)
	{
		ST[rt]=c;
		return;
	}
		
	int mid=(L+R)>>1;
	if(p<=mid)
		Update(ls,p,L,mid,c);
	if(p>mid)
		Update(rs,p,mid+1,R,c);
	Push_up(rt);
}
int Query(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt];
	int ans=-INF;
	int mid=(L+R)>>1;
	if(l<=mid)
		ans=max(ans,Query(ls,l,r,L,mid));
	if(r>mid)
		ans=max(ans,Query(rs,l,r,mid+1,R));
	return ans;
}
int main()
{
	while(~scanf("%d %d",&N,&M))
	{
		Build(1,1,N);
		while(M--)
		{
			scanf("%s %d %d",&op,&x,&y);
			if(op[0]=='Q')
			{
				printf("%d\n",Query(1,x,y,1,N));
			}	
			else if(op[0]=='U')
			{
				Update(1,x,1,N,y);
			}
		}
	}
	return 0;
}

3、C - A Simple Problem with Integers POJ - 3468
类型:区间更新,区间查询
题意:两种操作
“C a b c” means adding c to each of Aa, Aa+1, … , Ab.
“Q a b” means querying the sum of Aa, Aa+1, … , Ab.
思路:叶节点维护一个数值,根节点维护区间和
更新到所包含区间(l<=L&&R<=r),查询到所包含区间(l<=L&&R<=r)
所有改变累积到lazy数组上,向下推时做更新

const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,Q;char op[10];
ll ST[maxn<<2],lazy[maxn<<2];

void Push_up(int rt)
{
	ST[rt]=ST[ls]+ST[rs];
} 
void Push_down(int rt,int L,int R)
{
	if(lazy[rt])
	{
		lazy[ls]+=lazy[rt];
		lazy[rs]+=lazy[rt];
		int mid=(L+R)>>1;
		ST[ls]+=(mid-L+1)*lazy[rt];
		ST[rs]+=(R-mid)*lazy[rt];
		lazy[rt]=0;
	}
}
void Build(int rt,int L,int R)
{
	if(L==R)
	{
		scanf("%lld",&ST[rt]);
		return;
	}
	ll mid=(L+R)>>1;
	Build(ls,L,mid);
	Build(rs,mid+1,R);
	Push_up(rt);
} 
void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		lazy[rt]+=c;
		ST[rt]+=(R-L+1)*c;
		return;
	}
	Push_down(rt,L,R);
	ll mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
	Push_up(rt);
}
ll Query(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt];
	Push_down(rt,L,R);
	ll ans=0;
	ll mid=(L+R)>>1;
	if(l<=mid)
		ans+=Query(ls,l,r,L,mid);
	if(r>mid)
		ans+=Query(rs,l,r,mid+1,R);
	return ans;
}

int main()
{
    while(~scanf("%d %d",&N,&Q))
    {
    	Build(1,1,N);
    	while(Q--)
    	{
    		scanf("%s",op);
    		if(op[0]=='Q')
    		{
    			int l,r;
    			scanf("%d %d",&l,&r);
    			printf("%lld\n",Query(1,l,r,1,N));
    		}
    		else if(op[0]=='C')
    		{
    			int l,r,c;
    			scanf("%d %d %d",&l,&r,&c);
    			Update(1,l,r,1,N,c);
    		}		
    	}
    }
	return 0;
}

4、D - Mayor’s posters POJ - 2528

const int maxn=1e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int ST[maxn<<3],visit[maxn];int ans,X[maxn<<1];
struct Poster
{
	int l,r;
}posters[maxn];
void Push_down(int rt){
	if(ST[rt])
	{
		ST[ls]=ST[rt];
		ST[rs]=ST[rt];
		ST[rt]=0;
	}
}
void Update(int rt,int l,int r,int L,int R,int c){
	if(l<=L&&R<=r)
	{
		ST[rt]=c;
		return;
	}
	Push_down(rt);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
}
void Query(int rt,int L,int R){
	if(!visit[ST[rt]]&&ST[rt]!=0)
	{
		ans++;
		visit[ST[rt]]=1;
		return;
	}
	if(visit[ST[rt]]|L==R)
		return;
	int mid=(L+R)>>1;
	Query(ls,L,mid);
	Query(rs,mid+1,R);
}
int main(){
	
	int T,n;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		rep(i,1,n)
		{
			scanf("%d %d",&posters[i].l,&posters[i].r);
			X[i*2-1]=--posters[i].l;
			X[i*2]=posters[i].r;
		}
		sort(X+1,X+1+2*n);
		int k=unique(X+1,X+1+2*n)-X-1;
		
		mes(ST,0);
		mes(visit,0);
		
		for(int i=1;i<=n;++i)
		{
			int l=lower_bound(X+1,X+1+k,posters[i].l)-X;
			int r=lower_bound(X+1,X+1+k,posters[i].r)-X-1;
			if(l<=r) 
				Update(1,l,r,1,k-1,i);
		}
		ans=0;
		Query(1,1,k-1);
		printf("%d\n",ans);
	}
	return 0;
}

4、Mayor’s posters POJ - 2528
类型:区间更新、染色、离散化
题意:报纸的高度确定,给出宽度的区间,将报纸按顺序覆盖,问最后能看到多少张报纸?
思路:离散化X轴的坐标,每个叶节点维护一个离散化之后的区间([ x[ i ] , x[ i ] + 1 ])上的颜色,根节点维护整个区间的颜色,当整个区间的颜色不一致时,区间不设颜色,即ST[ rt ]=-1
最后统计答案的时候,设置一个visit数组,表示该颜色是否访问。遍历整棵线段树,如果根节点是有颜色的,就不用访问下去了,如果是没有颜色的,就一直访问到叶节点为止

const int maxn=1e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int ST[maxn<<3],visit[maxn];int ans,X[maxn<<1];
struct Poster
{
	int l,r;
}posters[maxn];

void Push_down(int rt)
{
	if(ST[rt])
	{
		ST[ls]=ST[rt];
		ST[rs]=ST[rt];
		ST[rt]=0;
	}
}

void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		ST[rt]=c;
		return;
	}
	Push_down(rt);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
}

void Query(int rt,int L,int R)
{
	if(!visit[ST[rt]]&&ST[rt]!=0)
	{
		ans++;
		visit[ST[rt]]=1;
		return;
	}
	if(visit[ST[rt]]|L==R)
		return;
	int mid=(L+R)>>1;
	Query(ls,L,mid);
	Query(rs,mid+1,R);
}

int main()
{
	int T,n;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		rep(i,1,n)
		{
			scanf("%d %d",&posters[i].l,&posters[i].r);
			X[i*2-1]=--posters[i].l;
			X[i*2]=posters[i].r;
		}
		sort(X+1,X+1+2*n);
		int k=unique(X+1,X+1+2*n)-X-1;
		
		mes(ST,0);
		mes(visit,0);
		
		for(int i=1;i<=n;++i)
		{
			int l=lower_bound(X+1,X+1+k,posters[i].l)-X;
			int r=lower_bound(X+1,X+1+k,posters[i].r)-X-1;
			if(l<=r) 
				Update(1,l,r,1,k-1,i);
		}
		ans=0;
		Query(1,1,k-1);
		printf("%d\n",ans);
	}
	return 0;
}

F - Count the Colors ZOJ - 1610
类型:区间更新、染色、离散化(这题数据比较小,可以不离散化)
题意:给指定区间染成指定颜色,按颜色的大小排序,输出格式:颜色 出现段数
思路:同上题类似,数据只有8000可以不离散化处理。叶节点维护 [ x , x+1 ] 的颜色,根节点维护整个区间的颜色,当整个区间的颜色不一致时,区间不设颜色,即ST[ rt ]=-1
最后统计答案时,设置pre,表示上一个查询到的颜色,与当前查询到的颜色不同时,才会有段数的增加, mp[ ST[ rt ] ]++(mp是map,key是颜色,value是颜色的段数)
顺序就从左往右访问叶节点即可,也不是一定要访问到叶节点,碰到有颜色的根节点,可以直接返回。给出的代码是直接遍历到叶节点的,因为这样操作比较简单

1、非离散化写法

const int maxn=8000+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int n,l,r,c;int ST[maxn<<3],ans[maxn];
int pre;
void Push_down(int rt)
{
	if(ST[rt]!=-1)
	{
		ST[ls]=ST[rt];
		ST[rs]=ST[rt];
		ST[rt]=-1;		
	}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		ST[rt]=c;
		return;
	}
	if(ST[rt]==c)
		return;
	Push_down(rt);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
}
void Query(int rt,int L,int R)
{
	if(L==R)
	{
		if(ST[rt]!=-1&&ST[rt]!=pre)
			ans[ST[rt]]++;
		pre=ST[rt];
		return;
	}
	Push_down(rt);
	int mid=(L+R)>>1;
	Query(ls,L,mid);
	Query(rs,mid+1,R);
}

int main()
{
	while(~scanf("%d",&n))
	{
		mes(ST,-1);
		mes(ans,0);
		rep(i,1,n)
		{
			scanf("%d %d %d",&l,&r,&c);
			Update(1,l+1,r,1,8000,c);
		}
		pre=-1;
		Query(1,1,8000);
		rep(i,0,8000)
			if(ans[i])
				printf("%d %d\n",i,ans[i]);
		printf("\n");
	}
	return 0;
}

2、离散化写法

const int maxn=8000+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int n,l,r;int X[maxn<<1],ST[maxn<<2];
map<int,int> mp;int pre;
struct Line
{
	int l,r,c;
}lines[maxn];
void Push_down(int rt)
{
	if(ST[rt]!=-1)
	{
		ST[ls]=ST[rs]=ST[rt];
		ST[rt]=-1;
	}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		ST[rt]=c;
		return;
	}
	Push_down(rt);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
}
void Query(int rt,int L,int R)
{
	if(L==R)
	{
		if(ST[rt]!=-1&&pre!=ST[rt])
			mp[ST[rt]]++;
		pre=ST[rt];
		return;
	}
	Push_down(rt);
	int mid=(L+R)>>1;
	Query(ls,L,mid);
	Query(rs,mid+1,R);
}

void Query(int rt,int L,int R)
{
	if(ST[rt]!=-1&&pre!=ST[rt])
	{
		mp[ST[rt]]++;
		pre=ST[rt];
		return;		
	}
	if(L==R)
	{
		pre=ST[rt];
		return;
	}	
	Push_down(rt);	
	int mid=(L+R)>>1;
	Query(ls,L,mid);
	Query(rs,mid+1,R);
}

int main()
{
    while(~scanf("%d",&n))
    {
    	rep(i,1,n)
    	{
    		scanf("%d %d %d",&lines[i].l,&lines[i].r,&lines[i].c);
    		X[i*2-1]=lines[i].l;
    		X[i*2]=lines[i].r;
    	}
    	sort(X+1,X+1+n*2);
    	int k=unique(X+1,X+1+2*n)-X-1;
    	
    	mp.clear();
    	mes(ST,-1);
    	for(int i=1;i<=n;++i)
    	{
    		int l=lower_bound(X+1,X+1+k,lines[i].l)-X;
    		int r=lower_bound(X+1,X+1+k,lines[i].r)-X-1;
    		if(l<=r)
    			Update(1,l,r,1,k-1,lines[i].c);
    	}
    	
    	pre=-1;
    	Query(1,1,k-1);
    	map<int,int> ::iterator it;
    	for(it=mp.begin();it!=mp.end();it++)
    		printf("%d %d\n",it->first,it->second);
    	printf("\n");    		
    }  
	return 0;
}

6、Tunnel Warfare HDU - 1540
类型:区间合并、单点更新、最大连续区间
题意:三种操作
D a:摧毁a
Q a:查询与a联通的村庄数量
R a:恢复上一个被摧毁的村庄
思路:每个叶节点维护三个值,ll左边连续区间大小、rl右边连续区间的大小,ml该区间的最大连续区间的大小。根节点也是同样维护这三个值
Build时整个区间是连续的,直接赋值为R-L+1即可。Update时,有两种单点更新的操作,摧毁或者恢复村子。更新完叶节点,处理回溯操作。Query时,返回它的最大连续区间的值ml即可,类比与求区间和,直接返回区间的大小。分三种情况,一种是在左子树,一种是在右子树,还有一种就是横跨两边。

const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M,p;char op[10]; 
int stack[maxn];
int k;
struct Seg_tree
{
	int ll,rl,ml;
}ST[maxn<<2];
void Push_up(int rt,int L,int R)
{
	ST[rt].ll=ST[ls].ll;
	ST[rt].rl=ST[rs].rl;
	int mid=(L+R)>>1;
	if(ST[ls].ll==(mid-L+1))
		ST[rt].ll+=ST[rs].ll;
	if(ST[rs].rl==R-mid)
		ST[rt].rl+=ST[ls].rl;
	ST[rt].ml=max(max(ST[rt].ll,ST[rt].rl),ST[ls].rl+ST[rs].ll);
	ST[rt].ml=max(max(ST[ls].ml,ST[rs].ml),ST[rt].ml);
}
void Build(int rt,int L,int R)
{
	ST[rt].ll=R-L+1;
	ST[rt].rl=R-L+1;
	ST[rt].ml=R-L+1;
	if(L==R)
		return;
	int mid=(L+R)>>1;
	Build(ls,L,mid);
	Build(rs,mid+1,R);
}
void Update(int rt,int p,int L,int R,int c)
{
	if(L==R)
	{
		if(c)
		{
			ST[rt].ll=1;
			ST[rt].rl=1;
			ST[rt].ml=1;
		}
		else 
		{
			ST[rt].ll=0;
			ST[rt].rl=0;
			ST[rt].ml=0;
		}
		return;
	} 
	int mid=(L+R)>>1;
	if(p<=mid)
		Update(ls,p,L,mid,c);
	if(p>mid)
		Update(rs,p,mid+1,R,c);
	Push_up(rt,L,R);
}
int Query(int rt,int p,int L,int R)
{
	if(L==R||R-L+1==ST[rt].ml)
		return ST[rt].ml;
	int mid=(L+R)>>1;
	if(p<=mid)
	{
		if(p>=mid-ST[ls].rl+1)
			return Query(ls,p,L,mid)+Query(rs,mid+1,mid+1,R);
		else 
			return Query(ls,p,L,mid);
	}		
	if(p>mid)
	{
		if(p<=mid+1+ST[rs].ll-1)
			return Query(ls,mid,L,mid)+Query(rs,p,mid+1,R);
		else
			return Query(rs,p,mid+1,R);
	}
}
int main()
{
	while(~scanf("%d %d",&N,&M))
	{
		Build(1,1,N);
		k=0;
		while(M--)
		{
			scanf("%s",op);
			if(op[0]=='D')
			{
				scanf("%d",&p);
				Update(1,p,1,N,0);
				stack[++k]=p;		
			}
			else if(op[0]=='Q')
			{
				scanf("%d",&p);
				printf("%d\n",Query(1,p,1,N));
			}
			else if(op[0]=='R')
			{
				p=stack[k--];
				Update(1,p,1,N,1);
			}
		}		
	}
	return 0;
}

思路二:用maxx表示区间内被破坏村子最大的序号,用minn表示区间内被破坏村子最小的序号。叶节点维护最大值maxx和最小值minn,恢复村子时maxx=0,minn=n+1,破坏村子时maxx=c,minn=c。根节点维护区间的最大值maxx和最小值minn
每当查询一个点时,就查询该点左边区间的最大的被破坏村子maxx,以及该点右边的最小的被破坏的村子minn。统计答案,就是minn-maxx-1,当然如果是自己被破坏了答案就是minn-maxx
类型:单点更新

const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M;char op[10];
int stack[maxn];
int k,p;
struct Seg_tree
{
	int maxx,minn;
}ST[maxn<<2];
void Push_up(int rt)
{
	ST[rt].maxx=max(ST[ls].maxx,ST[rs].maxx);
	ST[rt].minn=min(ST[ls].minn,ST[rs].minn);
}
void Build(int rt,int L,int R)
{
	if(L==R)
	{
		ST[rt].maxx=0;
		ST[rt].minn=N+1;
		return;
	} 		
	int mid=(L+R)>>1;
	Build(ls,L,mid);
	Build(rs,mid+1,R);
	Push_up(rt);
}
void Update(int rt,int p,int L,int R,int c)
{
	if(L==R)
	{
		if(c)
		{
			ST[rt].maxx=c;
			ST[rt].minn=c;
		}
		else
		{
			ST[rt].maxx=0;
			ST[rt].minn=N+1;
		}
		return;
	}
	int mid=(L+R)>>1;
	if(p<=mid)
		Update(ls,p,L,mid,c);
	if(p>mid)
		Update(rs,p,mid+1,R,c);
	Push_up(rt);
}
int QueryMin(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt].minn;
	int mid=(L+R)>>1;
	int minn=INF;
	if(l<=mid)
		minn=min(QueryMin(ls,l,r,L,mid),minn);
	if(r>mid)
		minn=min(QueryMin(rs,l,r,mid+1,R),minn);
	return minn;
}
int QueryMax(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt].maxx;
	int mid=(L+R)>>1;
	int maxx=-INF;
	if(l<=mid)
		maxx=max(QueryMax(ls,l,r,L,mid),maxx);
	if(r>mid)
		maxx=max(QueryMax(rs,l,r,mid+1,R),maxx);
	return maxx;
}
int main()
{
	while(~scanf("%d %d",&N,&M))
	{
		Build(1,1,N);
		k=0;
		while(M--)
		{
			scanf("%s",op);
			if(op[0]=='D')
			{
				scanf("%d",&p);
				Update(1,p,1,N,p);
				stack[++k]=p;					 
			}		
			else if(op[0]=='R')
			{
				p=stack[k--];
				Update(1,p,1,N,0);
			}
			else if(op[0]=='Q')
			{
				scanf("%d",&p);
		int	temp=QueryMin(1,p,N,1,N)-QueryMax(1,1,p,1,N);
				if(temp)
					temp--;
				printf("%d\n",temp);
			}
		}
	}
	return 0;
}

Assign the task HDU - 3974
题意:给出一棵树,对父节点染色,相当于对所有的子节点染色
两种操作
C x:表示查询x点的颜色
T x y:把x点的颜色变为y
思路:题目中给出了树的结构,可以用dfs序来维护子节点范围。用线段树叶节点维护树中的每一个节点的颜色,根节点维护区间的颜色
类型:线段树维护dfs序

补充知识:dfs序

void dfs(int u)
{
	Start[u]=++time;
	for(int i=0;i<G[u].size();++i)
	{
		int v=G[u][i];
		if(!visit[v])
		{
			visit[v]=1;
			dfs(v);
		}
	}
	End[u]=time;
}

dfs序的时间戳,进栈的时候时间戳+1,出栈的时候时间戳保持不变。dfs序可以用来维护某节点所管理的子节点的范围

1号节点的时间戳是 4 --> 4
2号节点的时间戳是 1 --> 5
(1)其中1代表2号节点的进栈时间Start[2],5代表2号节点出栈的时间End[2]。
(2)进栈的时候时间戳会+1,1表示它是第1个进栈的
(3)2 --> 5代表第2个节点到第5个节点都是它的子孙节点
(4)对于线段树的区间[ 1 , 5 ],我们应该明确的是[ 1 , 5 ],实际上是对1、2、3、4、5这5个叶节点的维护。
用线段树维护dfs序,其实也就是 一个线段树中的叶节点对应一个dfs序中的节点。所以如果对2号节点染色,相当于对自己和自己的子孙节点染色,也就是对1 --> 5 这5个点染色。所以dfs序的进栈和出栈的时间戳1 --> 5,也就对应于线段树的区间 [ 1 , 5 ]

const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,M,N;int u,v,time;
int Start[maxn],End[maxn];
int visit[maxn];
vector<int> G[maxn];
char op[10];
int p,c;
int ST[maxn<<2];
void dfs(int v)
{
	Start[v]=++time;
	for(int i=0;i<G[v].size();++i)
	{
		int u=G[v][i];
		if(!visit[u])
		{
			visit[u]=1;
			dfs(u);
		}
	}
	End[v]=time;
}
void Push_down(int rt)
{
	if(ST[rt]!=-1)
	{
		ST[ls]=ST[rs]=ST[rt];	
		ST[rt]=-1;	
	}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		ST[rt]=c;
		return;
	}
	Push_down(rt);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
}
int Query(int rt,int l,int r,int L,int R)
{
	if(L==R&&L==l)
		return ST[rt];
	Push_down(rt);
	int mid=(L+R)>>1;
	if(l<=mid)
		return Query(ls,l,r,L,mid);
	if(r>mid)
		return Query(rs,l,r,mid+1,R);
}
int main()
{
    int Cas=0;
    scanf("%d",&T);
    while(T--)
    {
    	mes(visit,0);
    	mes(ST,-1);
    	
    	
    	scanf("%d",&N);
    	rep(i,1,N)
			G[i].clear();
			
    	rep(i,1,N-1)
    	{
    		scanf("%d %d",&u,&v);
			G[v].pb(u);	
    		visit[u]=1;
		}
		
		time=0;
    	rep(i,1,N)
    	{
    		if(!visit[i])
    		{
    			mes(visit,0);
    			dfs(i);
    			break;
    		}
    	}
    	
    	scanf("%d",&M);
    	printf("Case #%d:\n",++Cas); 
    	while(M--)
    	{
    		scanf("%s",&op);
    		if(op[0]=='C')
    		{
    			scanf("%d",&p);
    			printf("%d\n",Query(1,Start[p],End[p],1,N));
    		}
    		else if(op[0]=='T')
    		{
    			scanf("%d %d",&p,&c);
    			Update(1,Start[p],End[p],1,N,c);
    		}
    	}
    } 
	return 0;
}

9、Vases and Flowers HDU - 4614
题意:两种操作
1 A F:从第A个花瓶开始插入F朵花,如果瓶子有花了,就往后插入花,直到插完所有的花,或者没有瓶子插入花为止。输出插花的起点和终点
2 A B:清空第A个花瓶到第B个花瓶中的花,并输出清空的花的数量
思路:每个叶节点维护一个空瓶,1表示是空瓶,0表示有花了。根节点维护空瓶的数量,之所以这样设置,是为了便于根据指定范围查询空瓶的数量,对于操作1可以直接从A点开始,二分查找刚好F个空瓶的位置。对于操作2,可以直接查找区间[ A , B ]内空瓶的数量
过程:
1、先判断区间[ A , N ]内有没有空瓶,没有空瓶的话,输出 Can not put any one.
2、然后,空瓶的数量和放花的数量取小者,二分查找放置的起点和终点
题目中花瓶的范围是0n-1,而习惯于改成1n

总结:个人感觉这里的难点是有花的瓶子在哪里是不确定的,但是这里通过二分法,准确的找到了有F个空瓶的区间,避开了具体哪个是空瓶,哪个是有花的瓶子

补充知识:二分查找
在二分搜索中l或r不要出现mid-1这种形式,很容易陷在递归中无限循环
单调递增时,f(mid)>=k,r=mid。mid是终点,k所求解(x)对应的函数值(y)
单调递减时,f(mid)<=k,r=mid。mid是终点,k所求解(x)对应的函数值(y)
单调递增

while(l<r)
{
	int mid=(l+r)>>1;
	if(f(mid)>=k)
		r=mid;
	else
		l=mid+1;
}

while(l<r)
{
	int mid=(l+r)>>1;
	if(f(mid)<k)
		l=mid+1;
	else
		r=mid;
}

单调递减

while(l<r)
{
	int mid=(l+r)>>1;
	if(f(mid)<=k)
		r=mid;
	else
		l=mid+1;
}

while(l<r)
{
	int mid=(l+r)>>1;
	if(f(mid)>k)
		l=mid+1;
	else
		r=mid;
}
const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,N,M;
int op,x,y;
int lazy[maxn<<2],ST[maxn<<2];
void Push_up(int rt)
{
	ST[rt]=ST[ls]+ST[rs];
}
void Push_down(int rt,int L,int R)
{
	if(lazy[rt]!=-1)
	{
		lazy[ls]=lazy[rs]=lazy[rt];
		int mid=(L+R)>>1;
		ST[ls]=(mid-L+1)*lazy[rt];
		ST[rs]=(R-mid)*lazy[rt];
		lazy[rt]=-1;
	}
}
void Build(int rt,int L,int R)
{
	lazy[rt]=-1;
	if(L==R)
	{
		ST[rt]=1;
		return;
	}
	int mid=(L+R)>>1;
	Build(ls,L,mid);
	Build(rs,mid+1,R);
	Push_up(rt);
}
void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		lazy[rt]=c;
		ST[rt]=(R-L+1)*c;
		return;
	}
	Push_down(rt,L,R);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
	Push_up(rt);
}
int Query(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt];
	Push_down(rt,L,R);
	int ans=0;
	int mid=(L+R)>>1;
	if(l<=mid)
		ans+=Query(ls,l,r,L,mid);
	if(r>mid)
		ans+=Query(rs,l,r,mid+1,R);
	return ans;
}
int BinarySearch(int left,int num)
{
	int l=left,r=N;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(Query(1,left,mid,1,N)<num)
			l=mid+1;
		else
			r=mid;
	}
	return r;
} 
int main()
{
    scanf("%d",&T);
    while(T--)
    {   	
    	scanf("%d %d",&N,&M);
    	
    	Build(1,1,N);
    	while(M--)
    	{
    		scanf("%d %d %d",&op,&x,&y);
    		if(op==1)
		{
			x++;
			int cnt=Query(1,x,N,1,N);
			if(cnt==0)
			{
				printf("Can not put any one.\n");
				continue;				
			}		
			int l=BinarySearch(x,1);
			int r=BinarySearch(x,min(y,cnt));
			printf("%d %d\n",l-1,r-1);
			Update(1,l,r,1,N,0);	
		}		
		else if(op==2)
		{
			x++;
			y++;
			printf("%d\n",y-x+1-Query(1,x,y,1,N));
			Update(1,x,y,1,N,1);
		}
    	}
    	printf("\n");
    }   
	return 0;
}

1、Atlantis HDU - 1542
题意:求平面矩阵的覆盖面积
思路:离散化X轴,用lower_bound和upper_bound两个函数查找原x坐标对应的索引。每个叶节点维护离散化之后的小区间[ x[ i ] , x[ i+1 ] ]是否被覆盖,以及被覆盖的长度X[R+1]-X[L],根节点维护大区间被覆盖的长度。线段树第一个根节点ST[1].length,表示的是整个区间被覆盖的长度,此时累加 高度之差*ST[1].length 就可以得到最终的覆盖面积

关于Push_up函数:
关键在于回溯,先判断是否覆盖,如果覆盖了直接计算长度
如果没覆盖,且是叶节点,那就更新为0
如果都不是,表明是没被完全覆盖的根节点,长度为两个子节点的覆盖的长度。这里,子节点的覆盖长度,是通过其它下位线更新得到的,也就是在扫到这条扫描线之前,对该子节点已经做过更新。此时就可以直接回溯。
一个子节点的覆盖次数,是所有祖先的覆盖次数的总和。因为代码中是对区间做的更新,因此更新到完全覆盖的根节点,就不会再往下更新了,此时的子节点是被完全覆盖的

类型:扫描线、离散化

补充知识:扫描线可以计算矩形面积、周长,可以计算线段交点,可以实现多边形扫描转换,在图的处理方面经常用到

离散化知识:
离散与不离散的区别
不离散的时候是一个一个的点。为了表示scanlines[i].l和scanlines[i].r
函数的写法是:Update(1,scanlines[i].l,scanlines[i].r-1,scanlines[i].f);
离散的时候是一个又一个的段
写法是: int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,scanlines[i].f);

离散化操作
1、对n个元素的X数组去重后,有k个元素
int k=1;
for(int i=2;i<=n;++i)
if(X[i]!=X[i-1])
X[++k]=X[i];
2、更简单的去重方法,这样X数组变式去重后的数组
int tot=unique(X+1,X+1+2*N)-X-1;

在去重后的数组中:两个相同的二分查找
int l=lower_bound(X+1,X+1+tot,scanlines[i].l)-X;
int r=lower_bound(X+1,X+1+tot,scanlines[i].r)-X-1;

int l=upper_bound(X+1,X+1+tot,scanlines[i].l)-&X[1];
int r=upper_bound(X+1,X+1+tot,scanlines[i].r)-&X[1]-1;

在不去重的数组中,查找一段的方法
int l=lower_bound(A+1,A+1+n,l)-A;
int r=upper_bound(A+1,A+1+n,r)-A-1;

const int maxn=100+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
double X[maxn<<1];
int N;
struct Seg_tree
{
	int cover;
	double length;
}ST[maxn<<3];
struct ScanLine
{
	double l,r,h;
	int f;
ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
	{
	}
	ScanLine()
	{
	}	
	
	bool operator<(const ScanLine& b) const
	{
		return h<b.h;
	}
	
}scanlines[maxn<<1];

void Push_up(int rt,int L,int R)
{
	if(ST[rt].cover)
	{
		ST[rt].length=X[R+1]-X[L];
	}
	else if(L==R)
	{
		ST[rt].length=0;
	}
	else
	{
		ST[rt].length=ST[ls].length+ST[rs].length;
	}
}
void Update(int rt,int l,int r,int L,int R,int c){
	if(l<=L&&R<=r)
	{
		ST[rt].cover+=c;
		Push_up(rt,L,R);
		return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
	Push_up(rt,L,R);
}
int main()
{
    double x1,x2,y1,y2;
    int Cas=0;
    while(scanf("%d",&N)&&N)
    {
    	rep(i,1,N)
    	{
    		scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
    		scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
    		scanlines[i*2]=ScanLine(x1,x2,y2,-1);
    		X[i*2-1]=x1;
    		X[i*2]=x2;
    	}
    	sort(X+1,X+1+2*N);
    	sort(scanlines+1,scanlines+1+2*N);
    	
    	int k=unique(X+1,X+1+2*N)-X-1;
    	
    	mes(ST,0);
    	double ans=0;
    	for(int i=1;i<=2*N;++i)
    	{
    		int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
    		int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
    		if(l<=r)
    			Update(1,l,r,1,k-1,scanlines[i].f);
    		ans+=(scanlines[i+1].h-scanlines[i].h)*ST[1].length;
    	}
	printf("Test case #%d\nTotal explored area: %.2lf\n\n",
		++Cas,ans);
    }
	return 0;
}

2、Picture POJ - 1177
题意:给出矩形的坐标,求所有矩形覆盖后的周长
思路:分两块计算
横线的长度,就是每次扫描线扫过之后的覆盖长度减去上一次的长度的绝对值
竖线的长度,就统计两条扫描线之间矩阵的个数,两条扫描线的高度矩阵个数2
因此,题目转化为求两条扫描线之间矩阵的个数,而矩阵是上下对称的,也就是求一条扫描线上不连续线段的个数(num)
每个节点维护五个值:length表示区间长度、cover覆盖次数、num不连续线段数量、lc左端点是否覆盖、rc右端点是否覆盖

const int maxn=5000+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int n,X[maxn<<1];
struct ScanLine
{
	double l,r,h;
	int f;
ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
	{
	}
	ScanLine()
	{
	}
	bool operator<(const ScanLine&b) const
	{
		return h<b.h;
	}
}scanlines[maxn<<1];
struct Seg_tree
{
	int length,num,cover;
	bool lc,rc;
}ST[maxn<<2];
void Push_up(int rt,int L,int R)
{
	if(ST[rt].cover)
	{
		ST[rt].length=X[R+1]-X[L];
		ST[rt].lc=ST[rt].rc=1;
		ST[rt].num=1;
	}
	else if(L==R)
	{
		ST[rt].length=0;
		ST[rt].lc=ST[rt].rc=0;
		ST[rt].num=0;		
	}
	else
	{
		ST[rt].length=ST[ls].length+ST[rs].length;
		ST[rt].lc=ST[ls].lc;
		ST[rt].rc=ST[rs].rc;		ST[rt].num=ST[ls].num+ST[rs].num-(ST[ls].rc&ST[rs].lc);
	}
}
void Build(int rt,int L,int R)
{
	ST[rt].cover=0;
	ST[rt].lc=ST[rt].rc=0;		
	ST[rt].length=0;
	ST[rt].num=0;	
	if(L==R)
		return;
	int mid=(L+R)>>1;
	Build(ls,L,mid);
	Build(rs,mid+1,R);
} 
void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		ST[rt].cover+=c;
		Push_up(rt,L,R);
		return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
	Push_up(rt,L,R);
}
int main()
{
	while(~scanf("%d",&n)&&n)
	{
		double x1,y1,x2,y2;
		rep(i,1,n)
		{
			scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
			scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
			scanlines[i*2]=ScanLine(x1,x2,y2,-1);
			X[i*2-1]=x1;
			X[i*2]=x2;
		}
		sort(X+1,X+1+n*2);
		sort(scanlines+1,scanlines+1+n*2);
		
		int k=unique(X+1,X+1+2*n)-X-1;
		Build(1,1,k-1);
		
		int ans=0,pre=0;
		for(int i=1;i<=2*n;++i)
		{
		int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
		int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
			if(l<=r)
				Update(1,l,r,1,k-1,scanlines[i].f);
			ans+=abs(ST[1].length-pre);			ans+=(scanlines[i+1].h-scanlines[i].h)*ST[1].num*2;
			pre=ST[1].length;
		}		
		printf("%d\n",ans);
	}     
	return 0;
}

3、覆盖的面积 HDU - 1255
题意:给出多个矩阵,求矩阵中覆盖两次以上的面积
思路:每个节点维护三个值,once至少覆盖一次的长度、twice至少覆盖两次的长度、cover覆盖次数。其实,分类的方法有多种,关键是选择一个标准讨论

const int maxn=1e3+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,N;double X[maxn<<1];
struct Seg_tree
{
	double once,twice;
	int cover;
}ST[maxn<<3];
struct ScanLine
{
	double l,r,h;
	int f;
	ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
	{
	}
	ScanLine()
	{
	}	
	
	bool operator<(const ScanLine& b) const
	{
		return h<b.h;
	}
	
}scanlines[maxn<<1];

void Push_up(int rt,int L,int R)
{
	if(ST[rt].cover>=2)
		ST[rt].twice=X[R+1]-X[L];
	else if(L==R)
		ST[rt].twice=0;
	else if(ST[rt].cover==1)
		ST[rt].twice=ST[ls].once+ST[rs].once;
	else
		ST[rt].twice=ST[ls].twice+ST[rs].twice;
	
	
	if(ST[rt].cover)
		ST[rt].once=X[R+1]-X[L];
	else if(L==R)
		ST[rt].once=0;
	else
		ST[rt].once=ST[ls].once+ST[rs].once;
	
}

void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		ST[rt].cover+=c;
		Push_up(rt,L,R);
		return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
	Push_up(rt,L,R);
}
int main()
{
	double x1,y1,x2,y2;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&N);
		rep(i,1,N)
		{
			scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
			scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
			scanlines[i*2]=ScanLine(x1,x2,y2,-1);
			X[i*2-1]=x1;
			X[i*2]=x2;
		}
		sort(X+1,X+1+2*N);
		sort(scanlines+1,scanlines+1+2*N);
		int k=unique(X+1,X+1+2*N)-X-1;
		
		mes(ST,0);
		double ans=0;
		for(int i=1;i<=2*N;++i)
		{
		int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
		int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
			if(l<=r)
				Update(1,l,r,1,k-1,scanlines[i].f);
		ans+=(scanlines[i+1].h-scanlines[i].h)*ST[1].twice; 
		}
		printf("%.2lf\n",ans);
	}
       
	return 0;
}

4、Get The Treasury HDU - 3642
题意:求长方体中覆盖三次以上的体积
思路:体积覆盖3次转化为面积覆盖3次。离散化Z轴,在水平方向上求出面积覆盖3次以上的总面积。离散化Z轴,对每个体积包含[ Z[i] , Z[i+1] ]的立方体,抽出两条平面上的扫描线。利用这些线,先计算该区间内覆盖三次以上的水平面积,然后乘上 Z[ i+1 ] - Z[ i ]

其次,对于覆盖三次以上的面积的求解,分类方法挺多的。代码中采用的是第二种
once 整个区间覆盖一次 整个区间至少覆盖一次 区间内只覆盖一次
twice 整个区间覆盖两次 整个区间至少覆盖两次 区间内只覆盖两次
more 整个区间覆盖多次 整个区间至少覆盖多次 区间内覆盖多次

const int maxn=1e3+5,INF=0x3f3f3f3f;
const int mod=1e9+7;

int T,N;
int Z[maxn<<1],X[maxn<<1];

struct Seg_tree
{
	int once,twice,more;
	int cover;
}ST[maxn<<3];

struct ScanLine
{
	double l,r,h;
	int f;
	ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
	{
	}
	ScanLine()
	{
	}
	bool operator<(const ScanLine & b) const 
	{
		if(b.h==h)
			return f>b.f;
		return h<b.h;
	}
}scanlines[maxn<<1];

struct Cuboid
{
	int x1,y1,z1,x2,y2,z2;
}cuboids[maxn<<1];

void Push_up(int rt,int L,int R)
{
	if(ST[rt].cover>=3)
	{
		ST[rt].once=X[R+1]-X[L];
		ST[rt].twice=X[R+1]-X[L];
		ST[rt].more=X[R+1]-X[L];				
	}
	else if(ST[rt].cover==2)
	{
		ST[rt].more=ST[ls].once+ST[rs].once;
		ST[rt].once=X[R+1]-X[L];
		ST[rt].twice=X[R+1]-X[L];		
	}
	else if(ST[rt].cover==1)
	{
		ST[rt].more=ST[ls].twice+ST[rs].twice;
		ST[rt].once=X[R+1]-X[L];
		ST[rt].twice=ST[ls].once+ST[rs].once;		
	}
	else if(L==R)
	{
		ST[rt].more=0;
		ST[rt].once=0;
		ST[rt].twice=0;			
	}
	else
	{
		ST[rt].more=ST[ls].more+ST[rs].more;
		ST[rt].once=ST[ls].once+ST[rs].once;
		ST[rt].twice=ST[ls].twice+ST[rs].twice;			
	}
} 

void Update(int rt,int l,int r,int L,int R,int c)
{
	if(l<=L&&R<=r)
	{
		ST[rt].cover+=c;
		Push_up(rt,L,R);
		return;
	} 
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid,c);
	if(r>mid)
		Update(rs,l,r,mid+1,R,c);
	Push_up(rt,L,R);
}

int main()
{	
	scanf("%d",&T);
	int Cas=0;
	while(T--)
	{
		scanf("%d",&N);
		rep(i,1,N)
		{
			scanf("%d %d %d %d %d %d",&cuboids[i].x1,&cuboids[i].y1,&cuboids[i].z1,
			&cuboids[i].x2,&cuboids[i].y2,&cuboids[i].z2);
			Z[i*2-1]=cuboids[i].z1;
			Z[i*2]=cuboids[i].z2;
		}
		sort(Z+1,Z+1+2*N);
		int k=unique(Z+1,Z+1+2*N)-Z-1;
		
		ll ans=0;
		for(int i=1;i<k;++i)
		{
			int k1=0,k2=0;
			for(int j=1;j<=N;++j)
			{
				if(cuboids[j].z1<=Z[i]&&cuboids[j].z2>=Z[i+1])
				{
					scanlines[++k1]=ScanLine(cuboids[j].x1,cuboids[j].x2,cuboids[j].y1,1);
					scanlines[++k1]=ScanLine(cuboids[j].x1,cuboids[j].x2,cuboids[j].y2,-1);
					X[++k2]=cuboids[j].x1;
					X[++k2]=cuboids[j].x2;
				}
			}
			sort(scanlines+1,scanlines+1+k1);
			sort(X+1,X+1+k2);
			
			int k3=unique(X+1,X+1+k2)-X-1;
			mes(ST,0);
			
			for(int j=1;j<=k1;++j)
			{
				int l=lower_bound(X+1,X+1+k3,scanlines[j].l)-X;
				int r=lower_bound(X+1,X+1+k3,scanlines[j].r)-X-1;
				if(l<=r)
					Update(1,l,r,1,k3-1,scanlines[j].f);
				ans+=1ll*(scanlines[j+1].h-scanlines[j].h)*ST[1].more*(Z[i+1]-Z[i]);
			}			
		}
		printf("Case %d: %lld\n",++Cas,ans);
	}
	return 0;
}

5、Black and White UVALive - 8356
题意:给定一个N*N的矩阵,给出一些矩阵的翻转操作,黑变白或者白变黑,求最后的覆盖为黑色的区域面积
思路:用线段树维护翻转情况。每一个叶节点维护一层中每一格的翻转情况,黑为1,白为0。根节点维护总区间黑色的数量。
扫描线从下往上一层一层的扫描,先进行下位线的翻转操作,统计答案后,再进行上位线的操作取消某操作的影响

const int maxn=1e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;

int T,N,K;
int ST[maxn<<2],lazy[maxn<<2];

struct ScanLine
{
	int l,r,h,f;
	ScanLine(int l1,int r1,int h1,int f1):l(l1),r(r1),h(h1),f(f1)
	{
	}
	ScanLine()
	{
	}
	
	bool operator<(const ScanLine & b) const
	{
		if(h==b.h)
			return f>b.f;
		return h<b.h;
	}
}scanlines[maxn<<1];

void Push_up(int rt)
{
	ST[rt]=ST[ls]+ST[rs];
}

void Push_down(int rt,int L,int R)
{
	if(lazy[rt])
	{
		lazy[ls]^=1;
		lazy[rs]^=1;
		int mid=(L+R)>>1;
		ST[ls]=mid-L+1-ST[ls];
		ST[rs]=R-mid-ST[rs];
		lazy[rt]=0; 
	}
}

void Update(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
	{
		lazy[rt]^=1;
		ST[rt]=R-L+1-ST[rt];
		return;
	}
	Push_down(rt,L,R);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid);
	if(r>mid)
		Update(rs,l,r,mid+1,R);
	Push_up(rt);
}

int main()
{
    int x1,y1,x2,y2;
    scanf("%d",&T);
    while(T--)
    {
    	mes(ST,0);
    	mes(lazy,0);
    	
    	scanf("%d %d",&N,&K);
    	rep(i,1,K)
    	{
    		scanf("%d %d %d %d",&x1,&x2,&y1,&y2);
    		scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
    		scanlines[i*2]=ScanLine(x1,x2,y2,-1);
    	}
    	sort(scanlines+1,scanlines+1+2*K);
    	
    	int ans=0;
    	int p=1;
		for(int i=1;i<=N;++i)
		{
			while(scanlines[p].h==i&&scanlines[p].f==1)
			{
				Update(1,scanlines[p].l,scanlines[p].r,1,N);
				p++;
			}
			ans+=ST[1];
			while(scanlines[p].h==i&&scanlines[p].f==-1)
			{
				Update(1,scanlines[p].l,scanlines[p].r,1,N);
				p++;				
			}
		}
		printf("%d\n",ans);
    }
	return 0;
}

Tokitsukaze and Strange Rectangle
题意:给出n个点,用一个三根线组成的u型框,去包含其中的多个点,问有多少种不同的选法
思路:从上而下、从左往右,扫描每一个点,把同一行的点放在栈中,统一计算。在同一层中(y相同),每次多加入一个点之后的情况
1、如果是该层的第一个点,新增的答案数量是该点左上方点的个数,加上自己
2、如果是该层最后一个点(自己加的辅助的点),新增的答案数量是 前一个点左上方点的个数(包括自己)乘以 前一个点到该点左上方点的个数(不包括自己)
3、既不是第一也不是最后的点,新增数量为,前一个点左上方点的个数(包括自己)乘以 前一个点到该点左上方点的个数(包括自己)加上 前一个点到该点左上方点的个数(包括自己)
每个叶节点维护一个离散化之后的区间([ x[ i ] , x[ i ] + 1 ])上点是否扫描到,根节点维护区间上点的总数量

const int maxn=2e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
 
ll N,ST[maxn<<2];
ll X[maxn],Y[maxn];
 struct Point
{
	int x,y;
	bool operator<(const Point & b) const
	{
		if(y==b.y)
			return x<b.x;
		return y>b.y;
	}
}p[maxn];
 void Push_up(int rt)
{
	ST[rt]=ST[ls]+ST[rs];
}
 void Update(int rt,int p,int L,int R,int c)
{
	if(L==R)
	{
		ST[rt]=c;
		return;
	}
	int mid=(L+R)>>1;
	if(p<=mid)
		Update(ls,p,L,mid,c);
	if(p>mid)
		Update(rs,p,mid+1,R,c);
	Push_up(rt);
}
 ll Query(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt];
	ll ans=0;
	int mid=(L+R)>>1;
	if(l<=mid)
		ans+=Query(ls,l,r,L,mid);
	if(r>mid)
		ans+=Query(rs,l,r,mid+1,R);
	return ans;
}
int main()
{
    scanf("%d",&N);
    rep(i,1,N)
    {
    		scanf("%d %d",&p[i].x,&p[i].y);
    		X[i]=p[i].x;
    		Y[i]=p[i].y;
    }
    sort(X+1,X+1+N);
    sort(Y+1,Y+1+N);
    int k1=unique(X+1,X+1+N)-(X+1);
    int k2=unique(Y+1,Y+1+N)-(Y+1);
    rep(i,1,N)
    {
    	p[i].x=lower_bound(X+1,X+1+k1,p[i].x)-X;
    	p[i].y=lower_bound(Y+1,Y+1+k2,p[i].y)-Y;
    }
    sort(p+1,p+1+N);
    ll sum=0,cur=1;
    int stack[maxn];
    int i=1;
    int z;
    Update(1,k1+1,1,k1+1,1);
    while(i<=N)
    {
    	int top=0;
    	while(p[i].y==p[cur].y)
    	{
    		Update(1,p[i].x,1,k1+1,1);
    		stack[++top]=p[i].x;
    		i++;
    	}   	  	
 	stack[++top]=k1+1;   
		ll cnt=Query(1,1,stack[1],1,k1+1);
		sum+=cnt;	 	
    	for(int j=2;j<=top;++j)
    	{
    		ll l=Query(1,1,stack[j-1],1,k1+1);
    		ll r=Query(1,stack[j-1]+1,stack[j],1,k1+1);
			if(j==top)
    			sum+=l*(r-1);
    		else
    			sum+=l*r+r;
    	}	
    	cur=i;	
    }
	printf("%lld\n",sum);
	return 0;
}

1、Cow Tennis Tournament CodeForces - 283E
题意:有n个能力值,能力值越大越强,m次翻转。每次翻转,区间[a,b]内的能力颠倒。求解有多少个三元组(p,q,r),p打败q,q打败r,r打败p

思路:逆向思考,不符合三元组的就是其中有一个人打败了两个人。因此答案就是
其中ak是第k个人能够打败的人数
因此,题目转变为求每个人能够打败的人数。而每一个人,能够打败的是,在他左边变化偶数次的人,和在他右边变化奇数次的人。
同时对于一个区间[a,b]来说,在b的右边的能力是不受这个区间翻转的影响的。如果b右边未做过翻转,那么b右边的能力依旧是大于b左边的所有能力。因此每次统计完后,要进行以b为终点翻转操作,以取消影响对b右边能力的影响
同样用用线段树维护翻转操作。每一个叶节点维护每一个能力的翻转情况,1代表翻转数次,0代表翻转偶数次。根节点维护总区间翻转奇数次的数量

const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,K,A[maxn];
int ST[maxn<<2],lazy[maxn<<2];
vector<int> vl[maxn],vr[maxn];
void Push_up(int rt)
{
	ST[rt]=ST[ls]+ST[rs];
}
void Push_down(int rt,int L,int R)
{
	if(lazy[rt])
	{
		lazy[ls]^=1;
		lazy[rs]^=1;
		int mid=(L+R)>>1;
		ST[ls]=mid-L+1-ST[ls];
		ST[rs]=R-mid-ST[rs];
		lazy[rt]=0;
	}
}
void Update(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
	{
		lazy[rt]^=1;
		ST[rt]=R-L+1-ST[rt];
		return;
	}
	Push_down(rt,L,R);
	int mid=(L+R)>>1;
	if(l<=mid)
		Update(ls,l,r,L,mid);
	if(r>mid)
		Update(rs,l,r,mid+1,R);
	Push_up(rt); 
}
int Query(int rt,int l,int r,int L,int R)
{
	if(l<=L&&R<=r)
		return ST[rt];
	Push_down(rt,L,R);
	int ans=0;
	int mid=(L+R)>>1;
	if(l<=mid)
		ans+=Query(ls,l,r,L,mid);
	if(r>mid)
		ans+=Query(rs,l,r,mid+1,R);
	return ans;
} 
int main()
{
    int l,r;
    while(~scanf("%d %d",&N,&K))
    {
    	rep(i,1,N)
    		scanf("%d",&A[i]);
    	sort(A+1,A+1+N);
    	rep(i,1,K)
    	{
     		scanf("%d %d",&l,&r);   
			l=lower_bound(A+1,A+1+N,l)-A;
			r=upper_bound(A+1,A+1+N,r)-A-1;
			if(l<=r)
			{
				vl[l].pb(r);
				vr[r].pb(l);
			} 		
    	}
    	
    	ll ans=1ll*N*(N-1)*(N-2)/6;
    	int p1,p2;
    	for(int i=1;i<=N;++i)
    	{
    		p1=0,p2=0;
    		while(p1<vl[i].size())
    		{
    			Update(1,i,vl[i][p1],1,N);
    			p1++;
    		}
    		int beat=0;
    		if(i>1)
    			beat+=i-1-Query(1,1,i-1,1,N);
    		if(i<N)
    			beat+=Query(1,i+1,N,1,N);
    		ans-=1ll*beat*(beat-1)/2;
    		while(p2<vr[i].size())
    		{
    			Update(1,vr[i][p2],i,1,N);
    			p2++;
    		}
		} 	
		printf("%lld\n",ans);
    }
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值