【ZJOI2008】Risk(最小左转法)(点定位)(扫描线)

本文详细解析了一种解决平面划分问题的算法思路,通过扫描线点定位确定平面,特别关注于处理连通块和最小左转法的应用,提供了一份完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门


题解:

显然要做的事情就是确定平面划分然后上一遍扫描线点定位。

点定位的具体做法其实就是扫描线一下,找到自己上方第一条边,就能确定自己所在的平面了。

如果本身平面图上的点是连通的可以直接上最小左转法,但是题目里面第二个图那种就会出事。

解决方法是先搞出连通块,每个连通块最高的点用点定位的方法找到上方第一条线段,然后连到端点,这样不会破坏原来图的平面划分。然后上最小左转法。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define db double
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}template<typename T>T get(){
		char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';
		T num=c^48;while(isdigit(c=gc()))num=((num+(num<<2))<<1)+(c^48);
		return f?-num:num;
	}inline int gi(){return get<int>();}
}using namespace IO;

using std::cerr;
using std::cout;

struct Pnt{
	int x,y;Pnt(){}Pnt(int _x,int _y):x(_x),y(_y){}
	friend Pnt operator+(cs Pnt &a,cs Pnt &b){return Pnt(a.x+b.x,a.y+b.y);}
	friend Pnt operator-(cs Pnt &a,cs Pnt &b){return Pnt(a.x-b.x,a.y-b.y);}
	friend int operator*(cs Pnt &a,cs Pnt &b){return a.x*b.y-b.x*a.y;}
	int len()cs{return x*x+y*y;}db ang()cs{return atan2(y,x);}
};
struct cmpP{bool operator()(cs Pnt &a,cs Pnt &b){return a.x==b.x?a.y<b.y:a.x<b.x;}};

cs int N=8007,M=N<<1|7,K=607;

int n,m;

std::map<Pnt,int,cmpP> id;
Pnt p[N+K];int tot;
inline int get_id(cs Pnt &q){
	int &cur=id[q];if(cur)return cur;
	p[++tot]=q;return cur=tot;
}

int to[M],ec=1;db slope[M];
std::vector<int> E[N];
inline void adde(int u,int v){
	E[u].push_back(++ec),to[ec]=v,slope[ec]=(p[v]-p[u]).ang();
	E[v].push_back(++ec),to[ec]=u,slope[ec]=(p[u]-p[v]).ang();
}
inline bool cmp_e(int i,int j){
	return slope[i]==slope[j]?to[i]<to[j]:slope[i]<slope[j];
}

int nxt[M],bel[M],bel_p[K],ct;
bool near[N][N];

namespace Area{

void get_area(){
	for(int re i=1;i<=tot;++i)
		std::sort(E[i].begin(),E[i].end(),cmp_e);
	for(int re u=1;u<=tot;++u)
		for(int re i=0;i<(int)E[u].size();++i){
			int j=(i==0)?(E[u].size()-1):(i-1);
			nxt[E[u][i]^1]=E[u][j];
		}
	for(int re i=2;i<=ec;++i){
		if(bel[i])continue;bel[i]=++ct;
		for(int re j=nxt[i];j!=i;bel[j]=ct,j=nxt[j]);
	}
	for(int re i=2;i<=ec;++i)near[bel[i]][bel[i^1]]=true;
}

}

namespace ScanLine{

struct atom{int a,b,t,id;};
inline bool operator<(cs atom &a,cs atom &b){
	if(a.a==b.a&&a.t!=b.t)return a.t>b.t;
	if(a.a==b.a)return (p[a.b]-p[a.a])*(p[b.b]-p[a.a])<0;
	Pnt &p1=p[a.a],&p2=p[b.a];
	return p1.x==p2.x?p1.y>p2.y:p1.x<p2.x;
}
struct cmp_atom{
	bool operator()(cs atom &a,cs atom &b){
		if(a.a==a.b)return (p[b.b]-p[a.a])*(p[b.a]-p[a.a])<0;
		if(b.a==b.b)return (p[a.b]-p[b.a])*(p[a.a]-p[b.a])>0;
		int t1=(p[b.b]-p[a.a])*(p[b.a]-p[a.a]),t2=(p[b.b]-p[a.b])*(p[b.a]-p[a.b]);
		int t3=(p[a.b]-p[b.a])*(p[a.a]-p[b.a]),t4=(p[a.b]-p[b.b])*(p[a.a]-p[b.b]);
		return (t1<0&&t2<=0)||(t2<0&&t1<=0)||(t3>0&&t4>=0)||(t4>0&&t3>=0);
	}
};
std::set<atom,cmp_atom> S;
bool vis[N];int tp;
std::vector<atom> vec;

void dfs(int u){
	vis[u]=true;if(p[u].y>p[tp].y)tp=u;
	for(int re e:E[u])if(!vis[to[e]])dfs(to[e]);
}
void connect(){
	vec.clear();S.clear();
	for(int re i=1;i<=tot;++i)
		if(E[i].size()&&!vis[i])
			{dfs(tp=i),vec.push_back({tp,tp,1,0});}
	for(int re i=1;i<=tot;++i)
		for(int re e:E[i])
			if(p[to[e]].x>p[i].x){
				vec.push_back({i,to[e],0,0});
				vec.push_back({to[e],i,2,0});
			}
	std::sort(vec.begin(),vec.end());
	for(auto &t:vec){
		switch(t.t){
			case 0:S.insert(t);break;
			case 2:S.erase(S.find({t.b,t.a,0,0}));break;
			case 1:{
				auto it=S.lower_bound(t);
				if(it==S.begin())break;
				adde((--it)->b,t.a);
			}
		}
	}
}

void get_area(){
	vec.clear();S.clear();
	for(int re i=1;i<=n;++i)
		vec.push_back({i,i,1,0});
	for(int re i=1;i<=tot;++i)
		for(int re e:E[i])
			if(p[to[e]].x>p[i].x){
				vec.push_back({i,to[e],0,e^1});
				vec.push_back({to[e],i,2,e^1});
			}
	std::sort(vec.begin(),vec.end());
	for(auto &t:vec){
		switch(t.t){
			case 0:S.insert(t);break;
			case 2:S.erase(S.find({t.b,t.a,0,t.id}));break;
			case 1:{
				auto it=S.lower_bound(t);
				bel_p[t.a]=bel[(--it)->id];
			}
		}
	}
}

}

void Main(){n=gi(),m=gi();
	for(int re i=1;i<=n;++i){
		int x=gi(),y=gi();
		get_id(Pnt(x,y));
	}for(int re i=1;i<=m;++i){
		int x1=gi(),y1=gi();
		int x2=gi(),y2=gi();
		int u=get_id(Pnt(x1,y1));
		int v=get_id(Pnt(x2,y2));
		adde(u,v);
	}
	ScanLine::connect();
	Area::get_area();
	ScanLine::get_area();
	for(int re i=1;i<=n;++i){
		std::vector<int> ans;
		for(int j=1;j<=n;++j)
			if(i!=j&&near[bel_p[i]][bel_p[j]])
				ans.push_back(j);
		cout<<ans.size();
		for(int re t:ans)cout<<" "<<t;
		cout<<"\n";
	}
}

void file(){
#ifdef zxyoi
	freopen("risk.in","r",stdin);
//	freopen("risk.out","w",stdout);
#endif
}
signed main(){file();Main();return 0;}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值