线段树:ZOJ1610、ZOJ3635、ZOJ3633、ZOJ3349


ZOJ1610:

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=610

 

大意是有一段区间最大8000,然后给N次染色操作,每次将一个[X,Y]的区间染为C色,N次操作完成以后,请问最后能看见的颜色有哪些,对每一种能看见的颜色,有几段区间是该颜色。

如例子:

0 4 4
0 3 1
3 4 2
0 2 2
0 2 3

最后[0,2]是颜色3,[2,3]是颜色1,[3,4]是颜色2,所以输出为:

1 1
2 1
3 1

 

思路:肯定是用线段树啦,但是要注意端点的处理,一般的线段树端点是认为不能重叠的,比如区间[0,3] 是分为[0,1],[2,3]对吧,但是此题中端点可以重叠,[0,3]应当分为[0,1],[1,3],所以呢,为了适应我们的代码,我们将输入的区间要做一次处理使其成为端点不重叠的情况,方法是将区间[X,Y]--->[X *2, Y*2 -1],这样。 比如[0.1] --> [0, 1], [1,3] --->[2,5]两个的端点就不重叠了~

 

另外将N次操作添加到线段树中是容易的,然后我们需要从这棵树上统计我们想要的信息。

但注意   [ 2,3] 区间如果是颜色1 , 但是该区间会被分为 [2,2],[3,3],而我们不能认为颜色1出现了两次,但注意到这两个区间一定是连续访问的!所以我们可以保留一个pre颜色,如果pre颜色同当前颜色一样,则是连续的同色区间,此时不能在该颜色上加1.

 

代码还是挺好懂的,如下:

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

const int MAXN=16005;
int color[MAXN<<2];
int cnt[MAXN];

void build(int rt,int l,int r)
{
	memset(color,-1,sizeof(color));
	memset(cnt,0,sizeof(cnt));
}

void pushDown(int rt)
{
	if(color[rt]!=-2)
	{
		color[rt<<1]=color[rt<<1|1]=color[rt];
		color[rt]=-2;
	}
}

void update(int rt,int col,int l,int r,int L,int R)
{
	if(L<=l && r<=R)
	{
		color[rt]=col;
		return;
	}
	pushDown(rt);
	int m=(l+r)>>1;
	if (L<=m)
		update(rt<<1,col,l,m,L,R);
	if (R>m)
		update(rt<<1|1,col,m+1,r,L,R);
}

void count(int rt,int l,int r,int& pre)
{
	if (color[rt]!=-2)
	{
		if(pre!=color[rt])
			cnt[color[rt]]++;
		pre=color[rt];
		return;
	}
	int m=(l+r)>>1;
	count(rt<<1,l,m,pre);
	count(rt<<1|1,m+1,r,pre);
}


int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		build(1,0,MAXN);

		int x,y,c;
		for(int i=0;i<n;i++)
		{
			scanf("%d%d%d",&x,&y,&c);
			update(1,c,0,MAXN,2*x,2*y-1);
		}

		int pre=-1;
		count(1,0,MAXN,pre);
		for(int i=0;i<MAXN;i++)
		{
			if(cnt[i]!=0)
			{
				printf("%d %d\n",i,cnt[i]);
			}
		}
		printf("\n");
	}
}


 ZOJ3633

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4801

题目大意是给定一个数字序列如:1,2,3,1,2,现在给定一些查询,每个查询包含一个区间,如[1,4],然后做一个检查:从右往左检查区间的数,如果没有数是重复的,则输出OK,如果有,则输出最右边的那个,比如[1,3]中1,2,3没有重复返回OK,[1,4]中1重复,返回1,[1,5]中1,2重复,但2在右边返回2.

首先做一个预处理,将原数组每个位置的数改为左边跟它相同的数的位置,没有的话则-1,

比如 [1,2,3,1,2] --->[-1,-1,-1,0,1]

然后在这个区间上应用线段树,每个节点保存最大值。

然后在给定查询区间中返回这个区间的最大值,跟给定区间的左边界比较,如果比这个边界还要靠左边,则说明这个区间肯定没有重复,否则则有重复,且返回的这个位置的数即是要输出的数。

#include<cstdio>
#include<cstring>
#include<vector>
#include<map>
using namespace std;

const int MAXN=500000+10;

int mx[MAXN<<2];


void build(int rt,int l,int r)
{
	memset(mx,-1,sizeof(mx));
}

void update(int rt,int num,int l,int r,int n)
{
	if ( l== r)
	{
		mx[rt]=num;
		return;
	}
	int m=(l+r)>>1;
	if (n<=m)
		update(rt<<1,num,l,m,n);
	else
		update(rt<<1|1,num,m+1,r,n);
	mx[rt]=max(mx[rt<<1],mx[rt<<1|1]);
}

int query(int rt,int l,int r,int L,int R)
{
	if (L<=l&&r<=R)
	{
		return mx[rt];
	}
	int m=(l+r)>>1;
	int left=-1,right=-1;
	if (L<=m)
		left=query(rt<<1,l,m,L,R);
	if ( m<R)
		right=query(rt<<1|1,m+1,r,L,R);
	return max(left,right);
}

int main()
{
	int n;

	vector<int> dolls;
	vector<int> pre;
	map<int,int> rec;
	map<int,int>::iterator it;

	while(scanf("%d",&n)!=EOF)
	{
		build(1,0,n-1);
		dolls.resize(n);
		pre.resize(n);
		rec.clear();
		int x;
		for(int i=0;i<n;i++)
		{
			scanf("%d",&x);
			dolls[i]=x;
			if ((it=rec.find(x))==rec.end())
			{
				pre[i]=-1;
				rec.insert(it,pair<int,int>(x,i));
			}
			else
			{
				pre[i]=it->second;
				it->second=i;
			}	
		}

		for(int i=0;i<n;i++)
			update(1,pre[i],1,n,i);

		int m;
		scanf("%d",&m);
		for(int i=0;i<m;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			--x,--y;
			int ans=query(1,1,n,x,y);
			if( ans<x )
				printf("OK\n");
			else
				printf("%d\n",dolls[ans]);
		}
		printf("\n");
	}
}


ZOJ3635

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3635

题意:第一行一个数 n,表示一共有 n 个空位;第二行有 n 个数,第 i 个数 ai 表示第 i 个人要在坐在第 ai 个空位上(也就是说他前面有ai-1个空位);第三行一个数 m 表示有 m 次访问;第四行 m 次访问,每次访问输入一个整数 bi 表示第 bi 个人坐的位置标号。

分析:线段树,线段树里存放的是这一段里有多少个空位,从下往上更新。

代码:

#include<cstdio>
using namespace std;
const int MAXN=50005;

int empty[MAXN<<2];
int sit[MAXN];
int num[MAXN];

void build(int rt,int l,int r)
{
	empty[rt]=r-l+1;
	if ( l==r)
		return;
	int m=(l+r)>>1;
	build(rt<<1,l,m);
	build(rt<<1|1,m+1,r);
}

int update(int rt,int l,int r,int ith)
{
	empty[rt]--;
	if ( l==r )
		return l;
	int m=(l+r)>>1;
	if (empty[rt<<1]>=ith)
		return update(rt<<1,l,m,ith);
	else
		return update(rt<<1|1,m+1,r,ith-empty[rt<<1]);
}

int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		int m;
		build(1,1,n);
		for(int i=0;i<n;i++)
		{
			scanf("%d",&num[i]);
			sit[i]=update(1,1,n,num[i]);
		}
		scanf("%d",&m);
		int q;
		scanf("%d",&q);
		printf("%d",sit[q-1]);
		for(int i=1;i<m;i++)
		{
			scanf("%d",&q);
			printf(" %d",sit[q-1]);
		}
		printf("\n");
	}
}


 ZOJ3349

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3820

大概是给出一个序列,找出一个最长的子序列,相邻的两个数的差在d以内。

一般的DP很容易想到:

设DP[I]表示以Ai结尾的数字的最长子序列,那么dp[i] = max{ dp[j] +1 } , | A[i] - A[j] | <=d .

这是O(n2)的,因为如递归式中去找那个合适的j的操作是O(n)的,如果引入线段树根据  Ai去找 [ Ai - d , Ai+d  ]区间的最大值的话,可以把这个操作降为O(logn)

所以最后的复杂度是O(nlogn).

#include<cstdio>
 #include<cstring>
 #include<cstdlib>
 #include<iostream>
 #include<cmath>
 #include<algorithm>
 #include<vector>
 #define lson l,m,rt<<1
 #define rson m+1,r,rt<<1|1
 using namespace std;
 const int N=100000+10;
 int mx[N<<2];
 vector<int> X;
 int n,d,n1;
 int a[N];
 void init(){
     sort(X.begin(),X.end());
     n1=unique(X.begin(),X.end())-X.begin(); 
 }
 void pushup(int rt){
     mx[rt]=max(mx[rt<<1],mx[rt<<1|1]);
 }
 void update(int L,int v,int l,int r,int rt){
     if (l==r){
         if(v>mx[rt]) mx[rt]=v;
         return;
     }
     int m=(l+r)>>1;
     if (L<=m) update(L,v,lson);
     else update(L,v,rson);
     pushup(rt);
 } 
 int query(int L,int R,int l,int r,int rt){
     if (L<=l && r<=R){
         return mx[rt];
     }
     int m=(l+r)>>1;
     int t1=0,t2=0;
     if (L<=m) t1=query(L,R,lson);
     if (m< R) t2=query(L,R,rson);
     return max(t1,t2);
 }
 void work(){
     memset(mx,0,sizeof(mx));
     int ret=0;
     int t=lower_bound(X.begin(),X.begin()+n1,a[0])-X.begin();
     update(t,1,0,N-1,1);
     for (int i=1;i<n;i++){
         int l=lower_bound(X.begin(),X.begin()+n1,a[i]-d)-X.begin();
         int r=upper_bound(X.begin(),X.begin()+n1,a[i]+d)-X.begin()-1;
     //    cout<<i<<" "<<l<<" "<<r<<" ";
         int t=query(l,r,0,N-1,1);
     //    cout<<t<<endl;
         if (t+1>ret) ret=t+1;
         int c=lower_bound(X.begin(),X.begin()+n1,a[i])-X.begin();
         update(c,t+1,0,N-1,1);
     }
     printf("%d\n",ret);
 }
 int main(){
     while (~scanf("%d%d",&n,&d)){
         X.clear();
         for (int i=0;i<n;i++){
             scanf("%d",&a[i]);
             X.push_back(a[i]);
         }
         init();
         work();
     }
     return 0;
 }


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值