天天看点

高度平衡

高度平衡

Time Limit:10000MS  Memory Limit:65536K

Total Submit:108 Accepted:95 

Case Time Limit:1000MS

Description

在每天挤奶的时候,农民约翰的N头牛(1≤n≤50000)总是排成一列。有一天,约翰决定与他的牛们一起玩一个极限飞盘游戏。为了简单起见,他将从奶牛队列里面选一定范围内的奶牛来玩这个游戏。然而所有的牛对这个游戏都很感兴趣。农民约翰列出了Q份名单(1≤Q≤200000)和每个奶牛的高度(1≤高度≤1000000)。对于每一份名单,他想你帮助他确定在每份名单中高度最高的奶牛与高度最低的奶牛的高度差是多少。 

Input

第一行为N(1≤N≤50000)和Q(1≤Q≤200000);从第2行到第N+1行,每行一个数字,表示第i头牛的高度(1≤height≤1000000);从第N+2行到第N+Q+1行,每行两个整数A和B(1≤A≤B≤N),表示从第A头牛到第B头牛的范围。 

Output

从第一行到第Q行,每行一个整数,表示从第A头牛到第B头牛之间,最高牛与最矮牛的高度差。 

Sample Input

6 3
1
7
3
4
2
5
1 5
4 6
2 2

      

Sample Output

6
3
0

      

Source

USACO 2007 January Silver

——————————————————————————————————————————————————————————————————

法一:线段树;

#include<cstdio>
const int inf=100000000;
struct node{int a,b,l,r,min,max;};
node tree[1000005];
int tot,a[500005];
int min(int x,int y)
{
	if(x<y)return x;
	else return y;
}
int max(int x,int y)
{
	if(x>y)return x;
	else return y;
}
void make(int x,int y)
{
	int i=++tot;
	tree[i].a=x,tree[i].b=y,tree[i].min=inf,tree[i].max=0;
	if(x<y)
	{
		tree[i].l=tot+1;make(x,(x+y)/2);
		tree[i].r=tot+1;make((x+y)/2+1,y);
		tree[i].max=max(tree[tree[i].l].max,tree[tree[i].r].max);
		tree[i].min=min(tree[tree[i].l].min,tree[tree[i].r].min);
	}
	else tree[i].max=tree[i].min=a[x];
}
int find_min(int i,int x,int y)
{
	if(tree[i].a>y||tree[i].b<x)return inf;
	if(tree[i].a>=x&&tree[i].b<=y)return tree[i].min;
	else
	{
		int t=inf;
		if(tree[i].l)t=min(t,find_min(tree[i].l,x,y));
		if(tree[i].r)t=min(t,find_min(tree[i].r,x,y));
		return t;
	}
}
int find_max(int i,int x,int y)
{
	if(tree[i].a>y||tree[i].b<x)return 0;
	if(tree[i].a>=x&&tree[i].b<=y)return tree[i].max;
	else
	{
		int t=0;
		if(tree[i].l)t=max(t,find_max(tree[i].l,x,y));
		if(tree[i].r)t=max(t,find_max(tree[i].r,x,y));
		return t;
	}
}
int main()
{
	int i,j,n,x,y,q;
	scanf("%d%d",&n,&q);
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	make(1,n);
	for(i=1;i<=q;i++)
	{
		scanf("%d%d",&x,&y);
		int t,tt;
		t=find_min(1,x,y);
		tt=find_max(1,x,y);
		printf("%d\n",tt-t);
	}
	return 0;
}
           

法二:倍增(很经典!!!)

预处理:

dp思想:dp[i][j]表示从i起连续2^j个数中最小值(即表示区间[i,i+2^j-1]);

如;数列3,2,4,5,6,8,1,2,9,7;f[1][0]=3,f[1][1]=2,f[1][2]=2,f[1][3]=1,f[2][0]=2,f[2][1]=2,f[2][2]=2……

f[i][j]=min(f[i][j-1],f[i+2^(j-1)][j-1]);

ps:别问我怎么想到的!因为没学根本想不到,记住就好.;

询问:

区间[x,y]可以分成[x,x+2^k-1]和[y-2^k+1,y];

即f[x][k]和f[y-(1<<k)+1][k];

k=floor(log(y-x+1)/log(2));

_______________________________________________ 以下为代码表示______________________________________________________________________

#include<cstdio>

#include<iostream>

#include<algorithm>

#include<cmath>

#include<cstdlib>

#define inf 1<<30

using namespace std;

int dpmax[50005][20],n,m,dpmin[50005][20];

int main()

{

int i,j,x,y,Max,Min,k;

scanf("%d%d",&n,&m);

for(i=1;i<=n;i++)

{

scanf("%d",&dpmin[i][0]);

dpmax[i][0]=dpmin[i][0];

}

for(j=1;j<=floor(log(n)/log(2));j++)

for(i=1;i<=(n-(1<<j)+1);i++)

{

dpmin[i][j]=min(dpmin[i][j-1],dpmin[i+(1<<(j-1))][j-1]);

dpmax[i][j]=max(dpmax[i][j-1],dpmax[i+(1<<(j-1))][j-1]);

}

for(i=1;i<=m;i++)

{

Max=0;Min=inf;

scanf("%d%d",&x,&y);

k=floor(log(y-x+1)/log(2));

Max=max(dpmax[x][k],dpmax[y-(1<<k)+1][k]);

Min=min(dpmin[x][k],dpmin[y-(1<<k)+1][k]);

printf("%d\n",Max-Min);

}

return 0;

}