HDU - 4302 :Holedox Eating ,线段树、树状数组+二分,优先队列

Description

Holedox is a small animal which can be considered as one point. It lives in a straight pipe whose length is L. Holedox can only move along the pipe. Cakes may appear anywhere in the pipe, from time to time. When Holedox wants to eat cakes, it always goes to the nearest one and eats it. If there are many pieces of cake in different directions Holedox can choose, Holedox will choose one in the direction which is the direction of its last movement. If there are no cakes present, Holedox just stays where it is.
 

Input

The input consists of several test cases. The first line of the input contains a single integer T (1 <= T <= 10), the number of test cases, followed by the input data for each test case.The first line of each case contains two integers L,n(1<=L,n<=100000), representing the length of the pipe, and the number of events.
The next n lines, each line describes an event. 0 x(0<=x<=L, x is a integer) represents a piece of cake appears in the x position; 1 represent Holedox wants to eat a cake.
In each case, Holedox always starts off at the position 0.
 

Output

Output the total distance Holedox will move. Holedox don’t need to return to the position 0.
 

Sample Input

 
     
3 10 8 0 1 0 5 1 0 2 0 0 1 1 1 10 7 0 1 0 5 1 0 2 0 0 1 1 10 8 0 1 0 1 0 5 1 0 2 0 0 1 1
 

Sample Output

 
     
Case 1: 9 Case 2: 4 Case 3: 2
 

Hint

Source

2012 Multi-University Training Contest 1

//多校联合第一场中的一道题。题意:在一个长度为n的管道内有n个点(1-n),每个点都可能突然出现一块蛋糕。并且 有一头奶牛(反正是一种动物)初始站在原点0。
给出m个操作: <1>  0  x:表示在x这个点出现了一块蛋糕(可以叠加,并且奶牛每次只能吃一块)
                             <2> 1     :表示要求奶牛找到离它最近的一块蛋糕并把它吃掉,如果当前离它最近的有多个点,那么就选择它上一次走的方向前进。
最后要求的是所有操作后奶牛共走了多少步。
//很容易看出是点更新,区间访问问题,只是每次查询离当前点左右最近的点的方式有些技巧而已!  做比赛的时候用的是树状数组+二分写,比赛后看到他人用非常巧妙的方法——优先队列解决了,内存、速率高效。 现将本题三种解决(树状数组+二分,   线段树(其实和前面一样的)、  优先队列)方法附下:

(1)、树状数组+二分: 利用树状数组记录每个点之前(包括自身)的蛋糕之和。对当前点st 分别往左右两边二分找出左边第一个i它的sum[i-1]值小于sum[st]的  和右边第一个sum[i]大于sum[st-1]的就是要求的极左极右了。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define maxn 100050
#define infl -0x3f3f3f3f
#define infr 0xfffffff
#define lowbit(x) x&(-x)

int tree[maxn],n;
void BuildTree(int p,int d)
{
	while(p<=(n+1))
	{
		tree[p]+=d;
		p+=lowbit(p);
	}
}

int query(int p)
{
	if(p<=0)
		return 0;
	int ans=0;
	while(p)
	{
		ans+=tree[p];
		p-=lowbit(p);
	}
	return ans;
}

int main()
{
	int T,t=0,i;
	scanf("%d",&T);	
	while(T--)
	{
		int Q,op,x=1;
		memset(tree,0,sizeof(tree));
		scanf("%d%d",&n,&Q);
		for(i=1;i<=n+1;i++)
			BuildTree(i,0);
		int len=0,st=1,dic=1;
		while(Q--)
		{
			scanf("%d",&op);
			if(op==0)
			{
				scanf("%d",&x);
				BuildTree(x+1,1);
			}
			else
			{
				int left=st,right=n+1,mid,max_l,max_r;
				int flag=0,tmpr=query(st-1);
				while(left<=right)  //二分右边最近点
				{
					mid=(left+right)>>1;
					if((query(mid)-tmpr)>0)  //满足st到mid之间有蛋糕,rig继续靠左
					{
						right=mid-1, max_r=mid;  //max_r更新
						flag=1; 
					} 
					else
						left=mid+1;	
				}
				if(!flag) //右边不存在蛋糕
					max_r=infr; 
						
				left=1,right=st,flag=0;
				int tmpl=query(st);
				while(left<=right)
				{
					mid=(left+right)>>1;	
					if(tmpl-query(mid-1)>0) //满足mid到st之间有蛋糕,lef继续靠右
					{
						left=mid+1, max_l=mid;
						flag=1; 
					} 
					else
						right=mid-1;
				}
				if(!flag)
					max_l=infl;
				int t1=st-max_l,t2=max_r-st;
				if(!(max_r==infr&&max_l==infl))
				{
					if(t1t2)
						len+=t2,dic=1,st=max_r;	
					else
					{
						if(dic==0) len+=t1,st=max_l;
						else len+=t2,st=max_r;
					}
					BuildTree(st,-1);
				}
			}
		}
		printf("Case %d: %d\n",++t,len);	
	}
	return 0;
}


(2)、线段树:和(1)一样,tree[]中记录每个点之前所有点的蛋糕总数,线段树本身就是二分查询的思想,所以等同于利用二分查询极左极右值。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define maxn 100005
#define infl -0x3f3f3f3f
#define infr 0x3f3f3f3f
#define L(x) x<<1
#define R(x) x<<1|1

struct data
{
	int l,r,sum;
	int Mid() { return (l+r)>>1; }
}tree[3*maxn];

void BuildTree(int p,int l,int r)
{
	tree[p].l=l,tree[p].r=r,tree[p].sum=0;
	if(l==r)
		return;
	int mid=(l+r)>>1;
	BuildTree(L(p),l,mid);
	BuildTree(R(p),mid+1,r);
}

void Updata(int p,int x,int d)
{
	if(tree[p].l==x&&tree[p].r==x)
	{
		tree[p].sum+=d; 
		return; 
	} 
	int mid=tree[p].Mid();
	if(x<=mid)
		Updata(L(p),x,d);
	else 
		Updata(R(p),x,d);
	tree[p].sum=tree[L(p)].sum+tree[R(p)].sum; 
}

int queryl(int p,int l,int r) //查询左边最近点
{
	if(!tree[p].sum)
		return infl;
	if(tree[p].l==tree[p].r)
		return r;
	int mid=tree[p].Mid();
	if(r<=mid)
		return queryl(L(p),l,r);
	else if(l>mid)
		return queryl(R(p),l,r);
	else
	{
		int max_r=queryl(R(p),mid+1,r); //左边最近点则值越大越好,所以优先选择右子树查询
		if(max_r>=0)
			return max_r;
		else return queryl(L(p),l,mid);
	}
}

int queryr(int p,int l,int r) //查询右边最近点
{
	if(!tree[p].sum)
		return infr;
	if(tree[p].l==tree[p].r)
		return l;
	int mid=tree[p].Mid();
	if(r<=mid)
		return queryr(L(p),l,r);
	else if(l>mid)
		return queryr(R(p),l,r);
	else
	{
		int max_l=queryr(L(p),l,mid); //值越小越好,优先选择左子树查询
		if(max_l(tmpr-st))
				{
					len+=tmpr-st,st=tmpr,dic=1;
					Updata(1,st,-1);
				}
				else 
				{
					if(dic==0)
						len+=st-tmpl,st=tmpl,Updata(1,st,-1);
					else
						len+=tmpr-st,st=tmpr,Updata(1,st,-1);
				}
			}
		}
		printf("Case %d: %d\n",++t,len);
	}
	return 0;
}


(3)、优先队列: 用一个降序优先队列ql 来存储小于等于st 的存在蛋糕的点,,一个升序优先队列 qr 来存储大于st 的存在 蛋糕的点, 那么每次从两个队列(某一个或两个队列都为空时特殊判断)中取队头元素则肯定是离它左边最近和右边最近的点。之后比较即可!
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

struct node
{
	bool operator()(int a,int b)
	{
		return a>b;
	}
};

int main()
{
	int T,t=0,n,m;
	scanf("%d",&T);
	while(T--)
	{
		priority_queueql; //从大到小
		priority_queue,node>qr; //从小到大。
		while(!ql.empty())
			ql.pop();
		while(!qr.empty())
			qr.pop();
		scanf("%d%d",&n,&m);
		int st=0,len=0,dic=1;
		int op,x,tmp;
		while(m--)
		{
			scanf("%d",&op);
			if(op==0)
			{
				scanf("%d",&x);
				if(x<=st)
					ql.push(x);
				else
					qr.push(x);
			}
			else
			{
				if(!ql.size()&&!qr.size())
					continue;
				if(ql.size()&&!qr.size())
				{
					tmp=ql.top() , ql.pop();
					len+=st-tmp, dic=0, st=tmp;
				}
				else if(!ql.size()&&qr.size())
				{
					tmp=qr.top(), qr.pop();
					len+=tmp-st, dic=1, st=tmp;
				}
				else
				{
					int tmp1=ql.top(),tmp2=qr.top();
					if((st-tmp1)<(tmp2-st))
					{
						ql.pop();
						len+=st-tmp1, st=tmp1, dic=0;
					}
					else if((st-tmp1)>(tmp2-st))
					{
						qr.pop();
						len+=tmp2-st, st=tmp2, dic=1;
					}
					else
					{
						if(dic==0)
							ql.pop(),len+=st-tmp1,st=tmp1;
						else
							qr.pop(),len+=tmp2-st,st=tmp2;
					}
				}
			}	
		}
		printf("Case %d: %d\n",++t,len);
	}
	return 0;
}

//另外此题还有 平衡树和伸展树 两种做法,不懂!表示数据结构太强悍了,各种树!

你可能感兴趣的:(数据结构)