CodeForces 547B. Mike and Feet 線段樹

題意:

給定一個長度爲n的數組a

a中一個連續區間的strength是區間內的最小值,對x=1,2,...,n分別求長度爲x的連續區間中,strength的最大值是多少


思路:

對於每個a[i]找出在a[i]左邊,離a[i]最近且比a[i]小的數的下標,記爲VL[i],若不存在則VL[i]=0;

找出在a[i]右邊,離a[i]最近且比a[i]小的數的下標,記爲VR[i],若不存在則VR[i]=n+1;

(VL和VR數組可以用棧計算出,具體直接見代碼)

那麼,a[i]是區間(VL[i],VR[i])內的最小值,也就是說,a[i]會出現在長度爲1,2,...,VR[i]-VL[i]-1 的組的strength中,於是就用a[i]去更新這些最大值

易知a[i]不會成爲長度大於VR[i]-VL[i]-1的區間的最小值,也就不需要更新這些最大值。

現在給定一個數列ans[1],ans[2],..,ans[n],初值爲0

對於每個a[i],讓區間[1,VR[i]-VL[i]-1]內的ans都與a[i]取最大值

要實現這個操作,可以用線段樹。

在所有操作結束後一次性下傳所有最大值標記,然後輸出最後的ans數組即可。

看題解,第二點是不需要線段樹來實現的,第二步可以做到O(n)

首先用ans[i]代表從i往左全部都與ans[i]進行max操作,這就相當於差分的思想

於是,前面第一步,直接令ans[ VR[i]-VL[i]-1 ]與a[i]進行max操作

然後倒着線性遞推一遍ans[i]=max(ans[i],ans[i+1])就可以了。


線段樹代碼(6400KB  124ms):

#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#define maxn 200007
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
using namespace std;
int n;
int a[maxn];
int vl[maxn];
int vr[maxn];
stack<int> s;
int ans[maxn<<2];
void PushDown(int rt){
	if(ans[rt]){
		ans[rt<<1]=max(ans[rt],ans[rt<<1]);
		ans[rt<<1|1]=max(ans[rt],ans[rt<<1|1]);
		ans[rt]=0;
	}
}
void build(int l,int r,int rt){
	if(l==r){
		ans[rt]=0;
		return;
	}
	int m=(l+r)>>1;
	build(ls);
	build(rs);
	ans[rt]=0;
}
void Add(int L,int R,int C,int l,int r,int rt){
	if(L <= l && r <= R){
		ans[rt]=max(ans[rt],C);
		return;
	}
	int m=(l+r)>>1;
	if(L <= m) Add(L,R,C,ls);
	if(R >  m) Add(L,R,C,rs);
}
void PushDownAll(int l,int r,int rt){
	if(l==r) {
		a[l]=ans[rt];
		return;
	}
	int m=(l+r)>>1;
	PushDown(rt);
	PushDownAll(ls);
	PushDownAll(rs);
}
int main(void)
{
	while(~scanf("%d",&n)){
		
		for(int i=1;i<=n;++i) scanf("%d",&a[i]);
		
		while(!s.empty()) s.pop();//初始化vl數組 
		for(int i=1;i<=n;++i){
			while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
			if(s.empty()) vl[i]=0;
			else vl[i]=s.top();
			s.push(i);
		}
		
		while(!s.empty()) s.pop();//初始化vr數組 
		for(int i=n;i>=1;--i){
			while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
			if(s.empty()) vr[i]=n+1;
			else vr[i]=s.top();
			s.push(i);
		}
		
		build(1,n,1);//初始化線段樹 
		for(int i=1;i<=n;++i){
			Add(1,vr[i]-vl[i]-1,a[i],1,n,1);//操作 
		}
		PushDownAll(1,n,1);//下傳所有標記並將結果存入a[i] 
		for(int i=1;i<=n;++i){//輸出答案 
			printf("%d",a[i]);
			if(i==n) printf("\n");
			else printf(" ");
		}
	}
return 0;
}



方法二代碼(4000KB  108ms):

#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <cstring>
#define maxn 200007
using namespace std;
int n;
int a[maxn];
int vl[maxn];
int vr[maxn];
stack<int> s;
int ans[maxn];
int main(void)
{
	while(~scanf("%d",&n)){
		
		for(int i=1;i<=n;++i) scanf("%d",&a[i]);
		
		while(!s.empty()) s.pop();//初始化vl數組 
		for(int i=1;i<=n;++i){
			while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
			if(s.empty()) vl[i]=0;
			else vl[i]=s.top();
			s.push(i);
		}
		
		while(!s.empty()) s.pop();//初始化vr數組 
		for(int i=n;i>=1;--i){
			while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
			if(s.empty()) vr[i]=n+1;
			else vr[i]=s.top();
			s.push(i);
		}
		
		memset(ans,0,sizeof(ans));
		for(int i=1;i<=n;++i){
			int maxLen=vr[i]-vl[i]-1;
			ans[maxLen]=max(ans[maxLen],a[i]);
		}
		
		for(int i=n-1;i>0;--i) ans[i]=max(ans[i],ans[i+1]);
		for(int i=1;i<=n;++i){//輸出答案 
			printf("%d",ans[i]);
			if(i==n) printf("\n");
			else printf(" ");
		}
	}
return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章