zoukankan      html  css  js  c++  java
  • [USACO Open08]牛的邻居Cow Neighborhoods解题报告

    题目


    分析

    首先我们来搞一搞这个模型。

    和一个点的曼哈顿距离<=C的区域大致长这样:



    中间红点就是我们的奶牛君。

    如果我们把坐标系转45°,换言之做变换:x'=x+y,y'=x-y,那么这个区域就会变成一个正方形,像这样:



    其实就是条件|x1-x2|+|y1-y2|<=C换成了|x1'-x2'|<=C且|y1'-y2'|<=C。可以自行枚举x1,x2,y1,y2的大小关系验证。

    把整个坐标系画出来,可能是这样:



    如果某头牛在另外一头牛的正方形区域内,它们就相邻。

    我们自然就有了一个naive的算法:枚举每两头牛,看它们是否相邻,若相邻就用并查集合并之。然而这个算法是O(N^2*并查集)的,并没有什么卵用……

    那怎么办呢?

    基础思路就是:我们只需要找离某头牛“最近的一圈”。



    假设有一头牛A,其右上方区域内y坐标最小的是B。那么,A的右上方所有和A相邻的牛,一定在B的上方且与B相邻。换言之,A右上方所有和A相邻的牛,都可以由A开始的一条“y坐标最小相邻牛”的连续链到达。

    所以我们只需要对每个点,找出其四个象限内y坐标最靠近它的点,并将它们的并查集合并即可。这可以用按y坐标从低到高扫描+线段树完成。只需要写一个象限的即可,其他的可以通过变换坐标做四次得到。

    代码

    <span style="font-size:14px;">#include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    typedef long long LL;
    #define Nil NULL
    const int SIZEN=100010;
    const int INF=0x7fffffff/2;
    class Point{
    public:
    	int x,y;
    	int id;
    };
    void print(const Point &p){
    	cout<<"("<<p.x<<" "<<p.y<<")";
    }
    bool operator < (const Point &a,const Point &b){
    	if(a.y==b.y) return a.x<b.x;
    	return a.y<b.y;
    }
    int N;
    int C;
    Point P[SIZEN];
    class Node{
    public:
    	int l,r;
    	Node *lc,*rc;
    	int t;
    	void push_up(void){
    		if(lc!=Nil){
    			t=P[rc->t]<P[lc->t]?lc->t:rc->t;
    		}
    	}
    	void modify(int x,int g){//新加一个横坐标为x的g号点
    		if(x>r||x<l) return;
    		if(x==l&&x==r){
    			if(P[t]<P[g]) t=g;
    		}
    		else{
    			lc->modify(x,g);
    			rc->modify(x,g);
    			push_up();
    		}
    	}
    	int query(int a,int b){
    		if(a>r||b<l) return 0;
    		if(l>=a&&r<=b) return t;
    		int g1=lc->query(a,b),g2=rc->query(a,b);
    		return P[g2]<P[g1]?g1:g2;
    	}
    };
    Node *build(int a,int b){
    	Node *p=new Node;
    	p->l=a,p->r=b;
    	if(a==b){
    		p->lc=p->rc=Nil;
    		p->t=0;
    	}
    	else{
    		int mid=(a+b)/2;
    		p->lc=build(a,mid);
    		p->rc=build(mid+1,b);
    		p->t=0;
    	}
    	return p;
    }
    int ufs[SIZEN]={0};
    int size[SIZEN]={0};
    int grand(int x){
    	return !ufs[x]?x:ufs[x]=grand(ufs[x]);
    }
    void merge(int a,int b){
    	int ga=grand(a),gb=grand(b);
    	if(ga==gb) return;
    	ufs[ga]=gb;
    	size[gb]+=size[ga];
    }
    int X[SIZEN];
    Point Q[SIZEN];
    void make_nearest(void){
    	P[0].x=P[0].y=-INF;
    	int tot=0;
    	for(int i=1;i<=N;i++) X[tot++]=P[i].x;
    	sort(X,X+tot);
    	tot=unique(X,X+tot)-X;
    	Node *root=build(0,tot-1);
    	memcpy(Q,P,sizeof(Q));
    	sort(Q+1,Q+1+N);
    	for(int i=1;i<=N;i++){
    		int l=lower_bound(X,X+tot,Q[i].x-C)-X;
    		int r=upper_bound(X,X+tot,Q[i].x)-X-1;
    		int q=root->query(l,r);
    		if(q&&abs(P[q].y-Q[i].y)<=C) merge(Q[i].id,q);
    		root->modify(lower_bound(X,X+tot,Q[i].x)-X,Q[i].id);
    	}
    }
    void work(void){
    	for(int i=1;i<=N;i++) size[i]=1;
    	for(int c1=-1;c1<=1;c1+=2){
    		for(int c2=-1;c2<=1;c2+=2){
    			for(int i=1;i<=N;i++){
    				P[i].x*=c1;
    				P[i].y*=c2;
    			}
    			make_nearest();
    		}
    	}
    	int cnt=0,mx=0;
    	for(int i=1;i<=N;i++){
    		if(ufs[i]==0){
    			cnt++;
    			mx=max(mx,size[i]);
    		}
    	}
    	printf("%d %d
    ",cnt,mx);
    }
    void read(void){
    	scanf("%d%d",&N,&C);
    	LL x,y;
    	for(int i=1;i<=N;i++){
    		scanf("%d%d",&x,&y);
    		P[i].x=x+y,P[i].y=x-y;
    		P[i].id=i;
    	}
    }
    int main(){
    	freopen("nabor.in","r",stdin);
    	freopen("nabor.out","w",stdout);
    	read();
    	work();
    	return 0;
    }
    </span>

    ps:我以前还左手CDQ右手单调队列……现在就只会扫描线+线段树了,药丸呐药丸
  • 相关阅读:
    搜索
    c++ map与unordered_map区别及使用
    01BFS
    宇智波程序笔记55-Flutter 混合开发】嵌入原生View-iOS
    宇智波程序笔记54-解Bug之路-记一次线上请求偶尔变慢的排查
    宇智波程序笔记53-从红黑树的本质出发,彻底理解红黑树!
    宇智波程序笔记52-最受欢迎的微服务框架概览
    宇智波程序笔记51-JDK 15安装及新特性介绍
    宇智波程序笔记50-解Bug之路-记一次线上请求偶尔变慢的排查
    宇智波程序笔记49-link JDBC Connector:Flink 与数据库集成最佳实践
  • 原文地址:https://www.cnblogs.com/wmdcstdio/p/7554236.html
Copyright © 2011-2022 走看看