• [APIO2018] New Home 新家


    前言

    给这道题跪了。

    给大巨佬 shadowice1984 跪了。

    题目

    洛谷

    LOJ

    讲解

    首先我们按套路肯定是将商店拆成开门和关闭两个时间点,然后按时间排序,用线段树维护即可。

    询问当然也是需要按时间排序的。

    然后考虑询问需要干什么:在某一个时间,询问商店种类是否齐全,以及最远距离。

    直接把商店看成 (k) 种颜色,也许会更为经典。

    二分答案

    我们直接二分距离,可以得到一个区间 ([l,r]),接下来我们要做的就是询问这个区间中是否存在 (k) 种颜色。

    区间数颜色

    我们记录一下每个颜色的前驱,如果前驱 (pre_i<l) 那么就累加贡献。

    但是这样的话我们就只能树套树解决,不但我不会,而且复杂度达到 (O(nlog_2^3(n))),显然过不去。

    这个时候,我们就只能转换思路,能否不把颜色的数量求出来。

    查询区间是否存在所有颜色

    如果想到这里,那么这道题就解决一半了。

    这里有一个妙妙转换,求区间 ([l,r]) 是否存在所有颜色,等价于求区间 ((r,+infty))最小的前驱 (pre) 是否在 ([l,r]) 中,可以自行画图理解,也可以简单意会。

    具体我们可以用 (k)(set) 维护每个颜色所有的坐标,查询前驱也很容易。

    为了不单独预处理右端点,我们可以在将所有颜色插入 (+infty),当然我们还需要插入一个左端点 (0) 避免找不到前驱。

    处理了前驱之后在线段树中修改一下即可。

    离散化

    为什么我说想到上面那步,这道题才解决一半呢?因为这道题的离散化相当恶心。

    由于可能出现多个商店在同一个坐标的情况,离散化相当烦人。

    由于 (set) 无法维护相同的坐标,如果存在多个 类型相同、坐标相同、开门时间有交集 的商店,(set) 无能为力,用 (multiset) 似乎也不是很方便,我的处理方式是将其合并。

    而对于 类型不同,坐标相同 的商店,我们的 (set) 虽然不会将它们混为一谈,但是线段树可做不到,我的处理方式是对这种类型的商店用它们原本的编号作为线段树的下标。注意这里指的商店是已经拆成开门和关门的两个时间点。

    剩下就是愉快的写代码时间了。

    代码

    这里是删掉调试信息,没删思路还顺手加了点注释的代码。
    //12252024832524
    #include <set>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define TT template<typename T>
    using namespace std; 
    
    typedef long long LL;
    const int MAXN = 300005 << 1;//双倍空间,下文有解释
    const int INF = 0x3f3f3f3f;
    int n,k,Q;
    int ans[MAXN],X[MAXN];
    
    LL Read()
    {
    	LL x = 0,f = 1;char c = getchar();
    	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
    	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
    	return x * f;
    }
    TT void Put1(T x)
    {
    	if(x > 9) Put1(x/10);
    	putchar(x%10^48);
    }
    TT void Put(T x,char c = -1)
    {
    	if(x < 0) putchar('-'),x = -x;
    	Put1(x); if(c >= 0) putchar(c);
    }
    TT T Max(T x,T y){return x > y ? x : y;}
    TT T Min(T x,T y){return x < y ? x : y;}
    TT T Abs(T x){return x < 0 ? -x : x;}
    
    struct shop
    {
    	int x,c,l,r;//坐标,颜色,开门时间段
    	bool operator < (const shop &px)const{
    		if(x ^ px.x) return x < px.x;
    		if(c ^ px.c) return c < px.c;
    		return l < px.l;
    	}
    }s[MAXN];
    struct nshop
    {
    	int opt,pos,c,t;//加点删点标记、线段树下标、颜色、时间
    	nshop(){}
    	nshop(int opt1,int pos1,int c1,int t1){
    		opt = opt1;
    		pos = pos1;
    		c = c1;
    		t = t1;
    	}
    	bool operator < (const nshop &px)const{
    		if(t ^ px.t) return t < px.t;
    		return opt < px.opt;
    	}
    }ns[MAXN];
    bool check(int l1,int r1,int l2,int r2)
    {
    	if(l2 <= l1 && l1 <= r2) return 1;
    	if(l2 <= r1 && r1 <= r2) return 1;
    	if(l1 <= l2 && l2 <= r1) return 1;
    	if(l1 <= r2 && r2 <= r1) return 1;
    	return 0;
    }
    set<int> p[MAXN]; //position
    
    #define lc (x<<1)
    #define rc (x<<1|1)
    int MIN[MAXN << 2];
    void up(int x){MIN[x] = Min(MIN[lc],MIN[rc]);}
    void Modify(int x,int l,int r,int pos,int val)
    {
    	if(l == r){MIN[x] = val;return;} 
    	int mid = (l+r) >> 1;
    	if(pos <= mid) Modify(lc,l,mid,pos,val);
    	else Modify(rc,mid+1,r,pos,val);
    	up(x);
    }
    void Build(int x,int l,int r)
    {
    	if(l == r) 
    	{
    		if(l > n) MIN[x] = 0;
    		else MIN[x] = INF;
    		return;
    	}
    	int mid = (l+r) >> 1;
    	Build(lc,l,mid); Build(rc,mid+1,r);
    	up(x);
    }
    int QueryMin(int x,int l,int r,int ql,int qr)
    {
    	if(ql <= l && r <= qr) return MIN[x];
    	int mid = (l+r) >> 1,ret = INF;
    	if(ql <= mid) ret = Min(ret,QueryMin(lc,l,mid,ql,qr));
    	if(mid+1 <= qr) ret = Min(ret,QueryMin(rc,mid+1,r,ql,qr));
    	return ret;
    }
    set<int>::iterator it1,it2;
    void solve(nshop A)
    {
    	if(!A.opt)//add
    	{
    		p[A.c].insert(A.pos);
    		it2 = it1 = p[A.c].find(A.pos);
    		++it2;
    		Modify(1,0,n+k,*it2,*it1);
    		it2 = it1; it2--;
    		Modify(1,0,n+k,*it1,*it2);
    	}
    	else
    	{
    		it2 = it1 = p[A.c].find(A.pos);
    		--it2;
    		Modify(1,0,n+k,*it1,INF);
    		++it1;
    		Modify(1,0,n+k,*it1,*it2);
    		p[A.c].erase(--it1);
    	}
    }
    bool ck(int x,int r)
    {
    	int retR = n+1,retL = 0,L = x-r-1,R = x+r+1;
    	int l = 1; r = n;
    	while(l <= r)
    	{
    		int mid = (l+r) >> 1;
    		if(X[mid] >= R) retR = mid,r = mid-1;
    		else l = mid+1;
    	}
    	l = 1;r = n;
    	while(l <= r)
    	{
    		int mid = (l+r) >> 1;
    		if(X[mid] <= L) retL = mid,l = mid+1;
    		else r = mid-1;
    	}
    	int val = QueryMin(1,0,n+k,retR,n+k);
    	if(retL < val && val < retR) return 1;
    	return 0;
    }
    
    struct Query
    {
    	int x,t,ID;
    	bool operator < (const Query &px)const{
    		return t < px.t;
    	}
    }q[MAXN];
    
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	n = Read(); k = Read(); Q = Read();
    	for(int i = 1;i <= n;++ i) s[i].x = Read(),s[i].c = Read(),s[i].l = Read(),s[i].r = Read();
    	sort(s+1,s+n+1);
    	int nn = 0;
    	for(int i = 1;i <= n;++ i)
    	{
    		int now = i;
            //类型相同、坐标相同、开门时间有交集
    		while(now < n && s[i].x == s[now+1].x && s[i].c == s[now+1].c && check(s[i].l,s[i].r,s[now+1].l,s[now+1].r))
    			s[i].l = Min(s[i].l,s[now+1].l),s[i].r = Max(s[i].r,s[now+1].r),++now;
    		++nn; ns[nn] = nshop(0,nn,s[i].c,s[i].l); X[nn] = s[i].x;
            //注意关门时间对应的线段树坐标要和开门时间一致,所以是nn-1
    		++nn; ns[nn] = nshop(1,nn-1,s[i].c,s[i].r+1); X[nn] = s[i].x;
    		i = now;
    	}
    	n = nn;
    	sort(ns+1,ns+n+1);
        //对每种颜色插入左端点0和右端点infty,由于右端点会被询问问到,所以不能相同,左端点就无所谓了,可以共用
    	for(int i = 1;i <= k;++ i) p[i].insert(0),p[i].insert(n+i);
    	//由于有右端点的存在,总点数会达到n+k也就是2n级别,记得空间开两倍
    	Build(1,0,n+k);
    	for(int i = 1;i <= Q;++ i) q[i].x = Read(),q[i].t = Read(),q[i].ID = i;
    	sort(q+1,q+Q+1);
    	int now = 1;
    	for(int i = 1;i <= Q;++ i)
    	{
    		while(now <= n && ns[now].t <= q[i].t) solve(ns[now++]);
    		int L = 0,R = 1e8,Ans = -1;
    		while(L <= R)
    		{
    			int mid = (L+R) >> 1;
    			if(ck(q[i].x,mid)) Ans = mid,R = mid-1;
    			else L = mid+1;
    		}
    		ans[q[i].ID] = Ans;
    	}
    	for(int i = 1;i <= Q;++ i) Put(ans[i],'
    ');
    	return 0;
    }
    /*
    这里是我写代码之前的写的流程
    感觉这道题要先把自己要做的事情写下来才不会写着写着就不知所措
    
    首先,读入商店与询问
    把类型与坐标都相同,且时间有交集的商店合并起来(不知道有没有这种数据)
    将商店坐标离散化,注意坐标相同也要搞成不同的
    把商店拆成开关门两个时间,用k个set处理前后缀(记得插入哨兵0,tt+k)
    对商店(双份)、询问按时间排序 
    搞一棵线段树[0,tt+k] ,要维护的仅仅是区间最小值 
    因为线段树上后面的哨兵需要每个类型的商店各来一个,而0永远不会被问到,所以0的prefix和suffix无关紧要
    然后按时间做询问,在询问之前将商店插的插、删的删
    前后缀用set找,然后在线段树上对应地方插入prefix 
    询问首先需要二分答案,假设二分的为ret,住的地方为x,那么我们要问 [x+ret+1,tt+k] 的最小值
    这个最小值要在 [x-ret,x+ret] 之间才行
    但是这样不够,因为离散化的时候距离已经不是原来的距离了,所以我们在线段树上面查询的不应该是 [x+ret+1,tt+k] 
    考虑我们是把实际距离转换成线段树上的距离 还是 线段树上的距离转换成实际距离
    答案是前者
    所以我们还需要对 x+ret+1 在离散化数组中搞一搞,换一换,假设其为 R,当然 x-ret-1也需要
    (这里可能常数会有点大,毕竟二分套二分是两个log,而且还有线段树上的询问 ,这里两个log的常数大概是4 
    这样我们问的就是 [R,tt+k] ,最小值需要在 (L,R) 中 
    */
    
  • 相关阅读:
    Vue.js $nextTick
    JS---函数名和变量名重名
    for循环中嵌套setTimeout,执行顺序和结果该如何理解?
    Rocket MQ整体简介
    ant Desgn Pro Vue 修改 title
    STS插件创建springboot项目,pom第一行报unkown错误
    c# 读取二进制文件并转换为 16 进制显示
    c# Winform 调用可执行 exe 文件
    按字节读取txt文件缓存区大小设置多少比较好?
    天翼云服务开放端口
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/15188610.html
Copyright © 2020-2023  润新知