NKOJ-2460 清理花瓶

来源:互联网 发布:陶哲轩 知乎 编辑:程序博客网 时间:2024/04/30 19:05

P2460清理花瓶
时间限制 : 10000 MS   空间限制 : 165536 KB
问题描述

Nicole是大家心中的女神,她每天都会收到很多束男生们送的鲜花。她有N个花瓶,编号0到N-1.每天她都会试着把收到的花束放到花瓶里,一个花瓶只能装一束花。她总是随机地选择一个花瓶A,如果该花瓶是空的,她就放一束花在里面,否则她会跳过这个花瓶。接着她会试着往A+1,A+2,...,N-1号花瓶里面放花,直到没有花了或者是第N-1号花瓶都被讨论了。然后她会扔掉剩下的花。有时Nicole也会去清理花瓶,因为花瓶太多了,所以她每次都随机选择一段花瓶清理。比如编号A到编号B这一段花瓶,她会把里面的花全都扔掉。

输入格式

第一行,两个整数N和M,表示花瓶的个数和操作的次数接下来M行,每行三个整数K,A,B。若K=1,表示今天Nicole收到了B束花,她打算从第A号花瓶开始放花。若K=2,表示今天Nicole要清理花瓶,她打算清理掉从第A号到第B号花瓶里的花(A <= B)。

输出格式

对于每次操作,输出一行结果:若K=1,输出Nicole放花的起止花瓶的编号。若一束花也放不进去,输出 “Can not put any one.”若k=2,输出Nicole清理掉的花的数目。

样例输入
输入样例1

10 51 3 52 4 51 1 82 3 61 8 8

输入样例2

10 61 2 52 3 41 0 82 2 51 4 41 2 3

样例输出
输出样例1

3 721 94Can not put any one.

输出样例2

2 620 944 52 3

提示

1 < N < 500011 < M < 50001

来源  改编自 multi 2013 contest 2

据说女主角是个男的

题解

分析解法

这道题目是一个赤裸裸线段树,首先从主角是个男性女生就可以看出
其次,插花嘛,而且还是一堆一堆地插,然后还一个区间一个区间地清,这些次要的条件也暗示了线段树

那么很棘手的是,这道题目要求输出的结果是插花的第一个和最后一个花瓶
所以自然而然地,就要考虑如何求出这两个值
我选择了最直白地直接在处理的时候就求出一个区间最左边的空花瓶和最右边的空花瓶的值

举例如下(此处使用区间[1,10],而题目中区间应为[0,9])

我们对每个区间,求出emp(空花瓶数),lemp(最左边的一个空花瓶编号),remp(最右边的一个空花瓶编号)对于一堆花瓶[1~10],假设此时它们的插花情况如下|花瓶|1|2|3|4|5|6|7|8|9|10||-|-|-|-|-|-|-|-|-|-|-||插花|1|1|0|0|1|0|1|0|0|1|现在要以L=2为最开始的一个花瓶,插入fl=4支花初始化修改的第一个瓶子res_l=0,最后一个瓶子res_r=11(两个瓶子都不存在,所以如果能够插花的话这两个值一定会更新)也就是说现在可以修改的区间为[2,10]①    当前剩余的待插的花数量为4,进行操作    对于[1,10] -> emp=5,lemp=3,remp=9    由于emp>4且左端点1<2,我们就转而对它的子区间进行操作    判断:        左子区间[l,5]与可以修改的区间[2,10]有交集  -> 修改[1,5]  -> 转入步骤②        右子区间[6,10]与可以修改的区间[2,10]有交集 -> 修改[6,10] -> 转入步骤⑥②    当前剩余的待插的花数量为4,进行操作    对于[1,5] -> emp=2,lemp=3,remp=4    由于左端点1<2,转而对它的子区间进行操作    判断过程同上:        转入步骤③⑤③    当前剩余的待插的花数量为4,进行操作    对于[1,3] -> emp=1,lemp=3,remp=3    由于左端点1<2,转而对它的子区间进行操作    判断同上:        由于左子区间emp=0了,就不详述操作        转入右子区间步骤④④    当前剩余的待插的花数量为4,进行操作    对于[3,3] -> emp=1,lemp=3,remp=3    左端点3>2且emp=1<4    将当前区间的空位全部插满,并更新:        待插的花的数量fl=fl-emp=3        那么更新区域的第一个瓶子res_l=min(res_l,lemp)=3                    最后一个瓶子res_r=max(res_r,remp)=3        emp=0,lemp=2,remp=4(表示当前无空瓶,此时的lemp,remp不重要了)    转回步骤③③    更新emp=emp[ls]+emp[rs](左子区间空瓶数+右子区间空瓶数)    由于右子区间已经插满,所以更新lemp=lemp[ls]=0(左子区间的第一个空瓶)    由于右子区间已经插满,所以更新remp=remp[rs]=4(右子区间的最后一个空瓶)    转回步骤②②    转入步骤⑤⑤    当前剩余的待插的花数量为4,进行操作    对于[4,5] -> emp=1,lemp=4,remp=4    左端点4>2且emp=1<4    将当前区间的空位全部插满,并更新:        那么更新区域的第一个瓶子res_l=min(res_l,lemp)=3                    最后一个瓶子res_r=max(res_r,remp)=4        待插的花的数量fl=fl-emp=2        emp=0,lemp=3,remp=6    转回步骤②②    更新        由于右子区间已经插满,所以更新lemp=lemp[ls]=0(左子区间的第一个空瓶)        由于右子区间已经插满,所以更新remp=remp[rs]=6(右子区间的最后一个空瓶)    转回步骤①①    转入步骤⑥...最后得到[1,10] -> emp=1,lemp=9,remp=9res_l=3,res_r=8

总结以下 步骤如下:

add(l,r)对于要修改的区间[l,r]①判断此时剩余的待插的花的数量fl,若fl==0,则直接退出②判断 区间[l,r]是否是可修改区间[L,n]的子集 和 区间可插入花的数量emp是否小于等于fl ,若都为是:    将区间填满,更新        res_l=min(res_l,lemp)        res_r=max(res_r,remp)        emp=0        lemp=r+1        remp=l-1 否则进入步骤③ ③分别判断[l,mid]和[mid+1,r]是否与可修改区间[L,n]有交集,若为是,则对该子区间进行add操作    并更新        emp=emp[ls]+emp[rs](左右子区间空花瓶之和)        lemp=(emp[ls])?(lemp[ls]:lemp[rs])(若左子区间有空瓶,那么更新为左子区间的lemp,反之则更新为右子区间的lemp)        remp=(emp[rs])?(remp[rs]:remp[ls])(同上)        //以上这两步更新的判断语句请自行思考,这个操作是核心        //此处不更新res_l和res_r是因为在对子区间进行操作时必然已经更新了,而且此处也无法进行更新,具体原因自行思考        //以上这两步思考起来并不困难(其实是我懒),如果不懂的话你就自己思考一下此处的lemp和remp该如何更新,以及res_l和res_r的更新位置

结束

注意

1、再次声明区间应该是[0,n-1]
2、请使用lazy进行优化
3、清除区间操作可根据上述操作推出,过程类似而且操作更简单,请独立自主,自强不息,不要随意抄代码,小心自生自灭(好像措辞不当…)
4、如果实在看不懂就根据我的操作目的自行思考解法,想不出的时候再看你就会豁然开朗了(你如果太内向我可能也救不了你)

附上对拍用代码(次要目的是参考,不得抄袭,当然你要抄我也没办法)

#include <iostream>#include <cstdio>#define mid (l+r>>1)using namespace std;int ll,rr,xl,xr,fl;int wait[401234],le[401234],re[401234],emp[401234];//wait相当于lazy,le相当于lemp,re相当于remp inline int input()//输入优化 {    char c=getchar();int o;    while(c>57||c<48)c=getchar();    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;    return o;}void PD(int ori,int l,int r)//lazy的操作 {    int ls=ori<<1,rs=ori<<1|1;    if(wait[ori]==1)    {//更新操作 ↓         emp[ls]=emp[rs]=0;        le[ls]=mid+1;le[rs]=r+1;        re[ls]=l-1;re[rs]=mid;        wait[ls]=wait[rs]=wait[ori];        wait[ori]=0;    }    else    {//更新操作 ↓         emp[ls]=mid-l+1;emp[rs]=r-mid;        le[ls]=l;le[rs]=mid+1;        re[ls]=mid;re[rs]=r;        wait[ls]=wait[rs]=wait[ori];        wait[ori]=0;    }}void add(int ori,int l,int r)//插花操作 ,ori为区间编号,[l,r]为当前区间 {    if(!fl)return;//特判,步骤①     int ls=ori<<1,rs=ori<<1|1;    if(wait[ori])PD(ori,l,r);    if(!emp[ori])return;    if(l>=ll&&emp[ori]<=fl)    {//更新操作 ↓             wait[ori]=1;            fl-=emp[ori];            xl=min(xl,le[ori]);xr=max(xr,re[ori]);            le[ori]=r+1;re[ori]=l-1;emp[ori]=0;    }    else    {        if(mid>=ll)add(ls,l,mid);        if(r>=ll)add(rs,mid+1,r);        //更新操作↓         le[ori]=(emp[ls])?le[ls]:le[rs];        re[ori]=(emp[rs])?re[rs]:re[ls];        emp[ori]=emp[ls]+emp[rs];    }}int clear(int ori,int l,int r)//清除操作 {    if(wait[ori])PD(ori,l,r);    int res=0;    if(ll<=l&&r<=rr)    {        le[ori]=l;        re[ori]=r;        res=r-l+1-emp[ori];        emp[ori]=r-l+1;        wait[ori]=-1;    }    else    {        if(wait[ori])PD(ori,l,r);        int ls=ori<<1,rs=ori<<1|1;        if(ll<=mid&&rr>=l)res+=clear(ls,l,mid);        if(ll<=r&&rr>mid)res+=clear(rs,mid+1,r);        le[ori]=emp[ls]?le[ls]:le[rs];        re[ori]=emp[rs]?re[rs]:re[ls];        emp[ori]=emp[ls]+emp[rs];    }    return res;}int main(){//  freopen("flower.in","r",stdin);//  freopen("flower.out","w",stdout);    int n=input(),m=input();bool k;    wait[1]=-1;le[1]=1;re[1]=n;emp[1]=n;    while(m--)    {        k=input()-1;        if(!k)        {            xl=n+1;xr=0;//相当于分析中的res_l,res_r             ll=input()+1;fl=input();//ll相当于分析中的L             add(1,1,n);            if(xr==0)puts("Can not put any one.");//一朵花都插不进去的时候xr是不会更新的             else printf("%d %d\n",xl-1,xr-1);        }        else         {            ll=input()+1;rr=input()+1;            printf("%d\n",clear(1,1,n));        }    }}