1. 程式人生 > >[UVa11235]Frequent values

[UVa11235]Frequent values

相同 names d+ clas 可能 拼接 出現的次數 排列 -s

題目大意:給一個非降序排列的整數數組a,你的任務是對於一系列詢問(i, j),回答ai,ai+1...aj中次數出現最多的值所出現的次數。

解題思路:由於是非降序排列,所有相同的數都是連在一起的。

本題可用RMQ做,但是我不會啊技術分享。 其實這題可以直接用線段樹做(什麽?RMQ可以用線段樹做?我還是不會啊),不過需要保存三個東西,該區間出現最多的數出現的次數,該區間最左邊的數出現的次數,該區間最右邊出現的次數。

為什麽要保存左邊和右邊的出現次數呢?例如區間$[1,10]$分為區間$[1,5]$和$[6,10]$,那麽$[1,5]$的右邊就有可能和$[6,10]$的左邊拼成一段出現次數更多的序列,所以要保存這兩個值,並計算兩個區間拼接後的最大值和原最大值哪個更優。查詢時同理。

C++ Code:

#include<iostream>
using namespace std;
int n,m,a[100001];
struct node{
	int Ls,Rs,s;
	node(int l=0,int r=0,int s=0):Ls(l),Rs(r),s(s){}
}d[400004];
inline int max(int a,int b){return(a>b)?a:b;}
void bt(int l,int r,int o){
	if(l==r){
		d[o]=node(1,1,1);
		return;
	}
	int mid=l+r>>1;
	bt(l,mid,o<<1);
	bt(mid+1,r,o<<1|1);
	d[o].s=max(d[o<<1].s,d[o<<1|1].s);
	if(a[mid]==a[mid+1])d[o].s=max(d[o].s,d[o<<1].Rs+d[o<<1|1].Ls);
	//如果左子區間的最右邊的值等於右子區間的最左邊的值,那麽兩邊就能拼起來,形成一個更長的段
	d[o].Ls=d[o<<1].Ls;
	d[o].Rs=d[o<<1|1].Rs;
	if(a[mid]==a[mid+1]){
		if(d[o].Ls==mid-l+1) 
		d[o].Ls+=d[o<<1|1].Ls;
		//如果左子區間的最右邊的值等於右子區間的最左邊的值,而左子區間剛好都是一個數,那麽就能和右邊拼起來 
		if(d[o].Rs==r-mid)
		d[o].Rs+=d[o<<1].Rs;
		//右子區同理 
	}
}
node query(int l,int r,int o,const int L,const int R,int ll,int rr){
	if(L<=l&&r<=R)return d[o];
	int mid=l+r>>1;
	node ld,rd,nd;
	if(L<=mid)ld=query(l,mid,o<<1,L,R,ll,mid);
	if(mid<R)rd=query(mid+1,r,o<<1|1,L,R,mid+1,rr);
	if(ld.s==0)return rd;
	if(rd.s==0)return ld;
	nd.s=max(ld.s,rd.s);
	if(a[mid]==a[mid+1])
	nd.s=max(nd.s,ld.Rs+rd.Ls);
	nd.Ls=ld.Ls;
	nd.Rs=rd.Rs;
	if(a[mid]==a[mid+1]){
		if(nd.Ls==mid-ll+1)
		nd.Ls+=rd.Ls;
		if(nd.Rs==rr-mid)
		nd.Rs+=ld.Rs;
	}
	return nd;
}
int main(){
	ios::sync_with_stdio(false);
	while(cin>>n>>m){
		for(int i=1;i<=n;++i)cin>>a[i];
		bt(1,n,1);
		while(m--){
			int x,y;
			cin>>x>>y;
			cout<<query(1,n,1,x,y,x,y).s<<endl;
		}
	}
	return 0;
}

[UVa11235]Frequent values