• 【APIO2018】新家(线段树)


    【APIO2018】新家(线段树)

    题面

    UOJ
    洛谷
    BZOJ

    题解

    论比赛时想不到二分的危害,就只能Cu滚粗

    既然不要在线,那么考虑离线做法。
    既然时间是区间,那么显然按照时间顺序处理答案。
    显然答案具有可二分性,那么对于当前位置而言,我们唯一要确定的就是([x-mid,x+mid])区间内是否所有种类的商店都至少出现过了一次。
    因为离线之后按照时间处理,因此已经没有了时间这一维,只剩下了位置和种类两个东西。那么问题就是二维数点,但是因为要求每个种类只数一次,所以似乎没法直接上树套树。
    既然要维护这个东西,显然需要额外记录当前颜色的前驱位置,假装为(pre_i)
    发现这样子仍然需要依次考虑每一个颜色,那么我们换一种方法。(pre_i)记录的既然是前驱位置,那么考虑([x+mid+1,inf))的每个种类的商店的前驱位置,如果前驱不在([x-mid,x+mid])的区间内,即证明区间内不存在这个种类的商店。既然多个商店可以在同一个位置,所以(pre_i)就记录和在(i)位置的所有商店中的最小前驱位置。这样子转为对于((i,pre_i))进行二维数点,并且只需要统计点数。
    那么考虑如何维护(pre_i)的值就好了。
    一个显而易见的想法就是对于每个种类开一个(set)维护其出现的所有位置。首先维护一下无解的情况,然后因为可能一种颜色并没有在([x+mid+1,inf))范围内出现,所以再额外维护一下每种颜色最后一个位置的最小值就好了。然而实际写的时候并不需要维护最小位置,可以强行在(inf)位置上给每个种类强行加一个进来,再在(-inf)位置上强制加一个,这样子就不存在没有前驱或者后继的情况了。
    最后的二分可以选择在线段树上二分,二分右端点最终直接减一下就是答案了。

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    #include<set>
    using namespace std;
    #define ll long long
    #define MAX 300300
    #define inf 1000000000
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int n,K,Q,Typecnt,ans[MAX];
    struct Work
    {
    	int y;//time
    	int x,t;//position,type
    	int opt;//add/del/query
    }p[MAX*3];
    int tot;
    bool operator<(Work a,Work b){if(a.y!=b.y)return a.y<b.y;return a.opt<b.opt;}
    multiset<int>::iterator it,itt;
    multiset<int> S[MAX],tt[MAX*20];
    struct Node{int mn,ls,rs;}t[MAX*20];
    int TOT,rt;
    void Modify(int &x,int l,int r,int p,int Add,int Del)
    {
    	if(!x)x=++TOT;
    	if(l==r)
    	{
    		if(Add)tt[x].insert(Add);
    		if(Del)tt[x].erase(tt[x].find(Del));
    		t[x].mn=(tt[x].empty()?inf:*tt[x].begin());
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(p<=mid)Modify(t[x].ls,l,mid,p,Add,Del);
    	else Modify(t[x].rs,mid+1,r,p,Add,Del);
    	t[x].mn=min(t[t[x].ls].mn,t[t[x].rs].mn);
    }
    int Query(int p)
    {
    	int l=0,r=inf,x=rt,mn=inf;
    	while(l<r)
    	{
    		int mid=(l+r)>>1,pos=min(t[t[x].rs].mn,mn);
    		if(p>mid||(mid-p<p-pos))l=mid+1,x=t[x].rs;
    		else r=mid,x=t[x].ls,mn=pos;
    	}
    	return l-p;
    }
    int main()
    {
    	n=read();K=read();Q=read();t[0].mn=inf;
    	for(int i=1;i<=n;++i)
    	{
    		int x=read(),t=read(),a=read(),b=read();
    		p[++tot]=(Work){a,x,t,1};
    		p[++tot]=(Work){b+1,x,t,-1};
    	}
    	for(int i=1;i<=Q;++i)
    	{
    		int x=read(),y=read();
    		p[++tot]=(Work){y,x,i,2};
    	}
    	sort(&p[1],&p[tot+1]);
    	for(int i=1;i<=K;++i)S[i].insert(inf),S[i].insert(-inf);
    	for(int i=1;i<=K;++i)Modify(rt,0,inf,inf,-inf,0);
    	for(int i=1;i<=tot;++i)
    	{
    		int opt=p[i].opt;
    		if(opt==1)//Add
    		{
    			it=itt=S[p[i].t].upper_bound(p[i].x);
    			--itt;
    			Modify(rt,0,inf,*it,p[i].x,*itt);
    			Modify(rt,0,inf,p[i].x,*itt,0);
    			if(S[p[i].t].size()==2)++Typecnt;
    			S[p[i].t].insert(p[i].x);
    		}
    		else if(opt==-1)//Del
    		{
    			it=itt=S[p[i].t].upper_bound(p[i].x);
    			--itt;--itt;
    			Modify(rt,0,inf,*it,*itt,p[i].x);
    			Modify(rt,0,inf,p[i].x,0,*itt);
    			S[p[i].t].erase(S[p[i].t].find(p[i].x));
    			if(S[p[i].t].size()==2)--Typecnt;
    		}
    		else ans[p[i].t]=Typecnt<K?-1:Query(p[i].x);
    	}
    	for(int i=1;i<=Q;++i)printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    递归调用简单的讲解
    有关杭电acm问题的分类
    【jquery】切换标题与内容/点击后动态切换当前状态
    将数组某键值抽出作为新数组
    table表格制作
    如何导入大sql文件到mysql数据库
    二维数组按照某一键值进行排序
    利用PHP输出某一目录所有文件
    php结合js动态获取空间时间
    ie6不支持minheight的解决方案
  • 原文地址:https://www.cnblogs.com/cjyyb/p/10201636.html
Copyright © 2020-2023  润新知