• [JZOJ5229]【GDOI2018模拟7.14】小奇的糖果


    题目

    在这里插入图片描述

    题目大意

    在一个二维的平面上,有一堆有颜色的点,你需要找出一条水平线段,使得这个线段上面(或者是下面)的点的颜色不包含所有的颜色。问点数最大是多少。


    思考历程

    在一开始,我看错了题目大意。
    题目说的是线段,而我理解的是直线。
    然后推了好多遍样例,觉得样例错了。
    后来才发现题目给的是线段。
    不然这题就是一个大大的水题了

    估计一下时间复杂度,嗯,应该是O(nlgn)O(n lg n)O(nlg2n)O(n lg^2 n)
    往这个方面想了好久,想不出来。
    于是退而求其次,想O(n2)O(n^2)
    这个时间复杂度还是很容易做的。
    显然地,我们肯定要先以纵坐标排一遍序。
    然后从上往下扫(后面还要从上往下扫)。
    对于每个横坐标,记录一下当前被扫过的点。
    那么,相当于是,在扫描线上,找到一个合法区间,使得这个区间最大。
    我们可以先固定左端点,然后右端点尽量延伸。用一个桶就可以了,还比较简单。
    然后就愉快地拿到了60分了。


    正解

    正解是O(nlgn)O(n lg n)的。
    假设我们取的是扫描线上面的点(下面的道理是一样的)。
    我们先将扫描线移到最下方,很显然,在这时候所有点都在扫描线的上面,这就是它们的终极状态。
    用一个树状数组维护一段横坐标的区间中的点数。
    在用一个双向链表来维护一下在它左边的离他最近的同颜色的点,右边同理。
    那么,对于一个点,它的前驱和它之间没有和它们同颜色的点,右边同理。
    我们先把终极状态的答案求出来,即是枚举哪个颜色不选,然后在没有这些颜色的区间中用树状数组求出点数,试着更新答案。
    接着我们考虑将扫描线向上移动。
    在移动的时候,有一些点去到了扫描线的下面,那么我们就要将它们在树状数组中的贡献减去,将它们从双向链表中删去。
    一个点从双向链表中删去后,它之前的前驱和后继之间没有和它颜色相同的点,所以我们可以这个区间试着更新答案。
    然后整一题就如此愉快地解决了。

    其中有一个很尴尬的地方是,由于一条扫描线可能同时扫过多个点,然而在统计这些答案的时候可能会对互相有影响。有点脑抽,然后想了很久,最终惊奇的发现,其实……
    我们可以先在树状数组中减去它的贡献,在扫描线换行的时候,我们就将它们从双向链表中删去,并统计答案。
    由于我们已经在树状数组中减去了它的贡献,那么在后面,如果一个点本应被删除,但是还没有删除就在前面的点中多计算了它的贡献,那么这个贡献是会被覆盖的。


    吐槽

    最近总喜欢吐槽一下,什么东西都吐槽一下。
    打代码的时候细节可能比较多,有时没有注意它在排序前后的对应位置,导致了我的程序调了好久,很不爽……
    (我的程序中,先对横坐标进行了排序,预处理了双向链表,然后再按纵坐标排序。可是,在排序前后的序号我在一开始忘记要对上,于是……)
    还有,为什么明明是我讲题,却在一群人对了这题之后才对?


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cassert>
    #define N 100000
    int n,m,tot;
    struct Candy{
    	int x,y,c;
    	int num; 
    } d[N+10];
    inline bool cmpx(const Candy &a,const Candy &b){
    	return a.x<b.x;
    }
    inline bool cmpy(const Candy &a,const Candy &b){
    	return a.y<b.y;
    }
    int ans;
    int t[N+10];
    #define lowbit(x) ((x)&(-(x)))
    inline int add(int k,int x){
    	for (;k<=m;k+=lowbit(k))
    		t[k]+=x;
    }
    inline int query(int k){ 
    	int res=0;
    	for (;k;k-=lowbit(k))
    		res+=t[k];
    	return res;
    }
    int w[N+10];//w[i]表示编号i的点(以横坐标排序之后的顺序)的编号
    int l[N+10],r[N+10],tmpl[N+10],tmpr[N+10];//这些东西记录的都是以横坐标排序后的编号
    int last[N+10];//记录某种颜色最后的点的编号
    inline void init(){
    	sort(d+1,d+n+1,cmpx);
    	m=0;
    	for (int i=1,lst=-2147483648;i<=n;++i){
    		if (d[i].x!=lst)
    			m++,lst=d[i].x;
    		d[i].x=m;
    	}
    	//以上这段是离散化(如果你开心,不离散化打动态开点线段树,我也没意见)
    	for (int i=1;i<=n;++i){
    		d[i].num=i; 
    		w[i]=d[i].x;
    	}
    	memset(last,0,sizeof last);
    	for (int i=1;i<=n;++i){
    		l[i]=last[d[i].c];
    		r[l[i]]=i;
    		last[d[i].c]=i;
    	}
    	for (int i=1;i<=tot;++i)
    		r[last[i]]=n+1;
    	memcpy(tmpl,l,sizeof l);
    	memcpy(tmpr,r,sizeof r);
    }
    int main(){
    	int T;
    	scanf("%d",&T);
    	while (T--){
    		scanf("%d%d",&n,&tot);
    		for (int i=1;i<=n;++i)
    			scanf("%d%d%d",&d[i].x,&d[i].y,&d[i].c);
    		init();
    		for (int i=1;i<=n;++i)
    			add(d[i].x,1);
    		w[0]=0;
    		w[n+1]=m+1;
    		ans=0;
    		for (int i=1;i<=tot;++i){
    			if (w[last[i]]+1<=m+1)
    				ans=max(ans,query(m)-query(w[last[i]]));
    			for (int j=last[i];j;j=l[j])
    				if (w[l[j]]+1<w[j])
    					ans=max(ans,query(w[j]-1)-query(w[l[j]]));
    		}
    		//以上是统计全部的答案(就是扫描线在全部点上面或下面的情况)
    		sort(d+1,d+n+1,cmpy);
    		d[0].y=d[n+1].y=-2147483648;
    		for (int i=1,j=1;i<=n;++i){
    			add(d[i].x,-1);
    			if (d[i].y!=d[i+1].y)
    				for (;j<=n && d[j].y<=d[i].y;++j){
    					int jj=d[j].num;
    					r[l[jj]]=r[jj];
    					l[r[jj]]=l[jj];
    					if (w[l[jj]]+1<w[r[jj]])
    						ans=max(ans,query(w[r[jj]]-1)-query(w[l[jj]]));
    				}
    		}
    		memset(t,0,sizeof t);
    		swap(l,tmpl),swap(r,tmpr);
    		for (int i=1;i<=n;++i)
    			add(d[i].x,1);
    		for (int i=n,j=n;i>=1;--i){
    			add(d[i].x,-1);
    			if (d[i].y!=d[i-1].y)
    				for (;j>=1 && d[j].y>=d[i].y;--j){
    					int jj=d[j].num;
    					r[l[jj]]=r[jj];
    					l[r[jj]]=l[jj];
    					if (w[l[jj]]+1<w[r[jj]])
    						ans=max(ans,query(w[r[jj]]-1)-query(w[l[jj]]));
    				}
    		}
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    总结

    有的时候链表是一个好东西。
    比如说在处理有没有相同颜色之类的问题的时候,用链表记录一下左右最近的同颜色的点或许是一个很好的解题方向。
    有的时候正着搞不容易,那就反着搞,全部加进去,然后删掉。
    还有在做平面问题时应当往扫描线上想一想。

  • 相关阅读:
    MySQL无法登录服务器解决方法
    photoshop mac版下载及破解
    静态html传参数
    flash与php 交互(as传参给php)
    PHP发送邮件类库PHPMailer的简单使用
    PHP CodeBase: 判断用户是否手机访问
    HTTP报文
    有关phpmailer的详细介绍及使用方法
    JS 实现 Tab标签切换功能
    new Option()——实现时间联动
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145255.html
Copyright © 2020-2023  润新知