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;
}