zoukankan      html  css  js  c++  java
  • [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;
    }
    

    总结

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

  • 相关阅读:
    Leetcode 191.位1的个数 By Python
    反向传播的推导
    Leetcode 268.缺失数字 By Python
    Leetcode 326.3的幂 By Python
    Leetcode 28.实现strStr() By Python
    Leetcode 7.反转整数 By Python
    Leetcode 125.验证回文串 By Python
    Leetcode 1.两数之和 By Python
    Hdoj 1008.Elevator 题解
    TZOJ 车辆拥挤相互往里走
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145255.html
Copyright © 2011-2022 走看看