hdu 4614 Vases and Flowers (线段树打标记)

本文介绍了一道经典的线段树应用问题——花瓶问题,并详细解析了解题思路与实现代码。通过维护线段树节点的状态,实现了花瓶的插花与取花操作。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4614

题意:给你N个花瓶编号为0~N-1,和M个操作,操作分为两种 

1.将B支花一次插入从A之后(包括A)的花瓶中,如果花瓶为非空就插入到其之后的花瓶中,每个花瓶限制只能插一枝花。输出第一次花的花瓶的位置和最后一次插花的花瓶的位置(如果一枝花也不能插输出~~~~)

2. 将A~B这个区间上的所有花取出,输出取出花的个数。

思路:线段树~ 线段树只维护一个域就是此区间上是否插了花,如果插的mark为0,没查mark为1,-1表示没有标记。 

下面是这道题目的关键,就是假设已经找到了插花的起点和终点,怎么快速的更新这个区间呢??? 方法是区间赋值,直降将整个区间全部都插上花,这样的话就能用打标记做了。

将区间中的花拔出来的方法同上。

如何快速查找到插花的位置呢? 先判断A点是否是插过花了,如果插过了就去找在A点右边且离A点最近的位置,记f(n)表示前n项的和,则f(s)=f(A)+1,s就是起点,终点和起点间有(B-1)个1,同样可以用前n项和的方法找到。

因为线段树维护的此区间是否全部为1或者为0,所以求和的方法跟维护和的时候是不太一样的,方法就是不断地下放当前位置的标记然后求和。

我的程序跑的1700ms+,看到最快的100ms+就能过,我简直是太弱了,不过是1A也很开心。

code:

#include <cstdio>
#include <cstdlib>
#include <iostream>

using namespace std;

typedef long long LL;

const int maxn=50021;

struct segtree
{
	int l,r,mark;
}ss[4*maxn+10];

int n;
void Build(int k,int ll,int rr)
{
	ss[k].l=ll;
	ss[k].r=rr;
	ss[k].mark=-1;
	if(ll==rr) return ;
	Build(2*k+1,ll,(ll+rr)/2);
	Build(2*k+2,(ll+rr)/2+1,rr);
}
void init(int n_)
{
	n=1;
	while(n<n_)n*=2;
	Build(0,0,n-1);
}
void Push_down(int k)
{
	ss[2*k+1].mark=ss[k].mark;
	ss[2*k+2].mark=ss[k].mark;
	ss[k].mark=-1;
}
void update(int a,int b,int k,int ff)
{
	if(b<ss[k].l||a>ss[k].r)   return ;
	if(a<=ss[k].l&&ss[k].r<=b)  ss[k].mark=ff;
	else{
		if(ss[k].mark!=-1) Push_down(k);
		update(a,b,2*k+1,ff);
		update(a,b,2*k+2,ff);
	}
}
int getsum(int a,int b,int k)
{
    if(ss[k].l>b||ss[k].r<a) return 0;
    if(a<=ss[k].l&&ss[k].r<=b&&ss[k].mark!=-1)  return (ss[k].r-ss[k].l+1)*ss[k].mark;
    else{
        if(ss[k].mark!=-1) Push_down(k);
        int v1=getsum(a,b,2*k+1);
        int v2=getsum(a,b,2*k+2);
        return v1+v2;
    }
}
int query(int sum,int k)
{
	if(ss[k].l==ss[k].r){
        if(sum==ss[k].mark) return ss[k].l;
        else return 0;
    }
	else{
		if(ss[k].mark!=-1) Push_down(k);
		int mid=getsum(ss[2*k+1].l,ss[2*k+1].r,0);
		if(sum>mid) query(sum-mid,2*k+2);
		else 		query(sum,2*k+1);
	}

}
int main()
{
	int T,N,M;
	int K,A,B,s,t,sum0,sum1,sum2;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&N,&M);
		init(N);
		update(0,N-1,0,1);
		update(N,n-1,0,0);
		while(M--){
			scanf("%d%d%d",&K,&A,&B);
			if(K==1){
				sum0=getsum(0,n-1,0);
				sum1=getsum(0,A,0);
				if(A==0) sum2=0;
				else     sum2=getsum(0,A-1,0);
				if(sum0-sum2==0) printf("Can not put any one.\n");
				else{
					if(sum1>sum2){
						s=A;
						if(sum0-sum2<B) t=query(sum0,0);
						else            t=query(sum1+B-1,0);
					}
					else{
						s=query(sum1+1,0);
                        if(sum0-sum2<B) t=query(sum0,0);
						else            t=query(sum1+B,0);
					}
					update(s,t,0,0);
					printf("%d %d\n",s,t);
				}
			}
			else{
				if(A==0) printf("%d\n",B+1-getsum(0,B,0));
				else 	 printf("%d\n",(B-A+1)-(getsum(0,B,0)-getsum(0,A-1,0)));
				update(A,B,0,1);
			}
		}
		printf("\n");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值