【平面图转对偶图】BZOJ4541 [Hnoi2016] 矿区

【题目】
BZOJ
给定一幅 n n n个点 m m m条边的平面图,定义一个区域的权值为其面积的平方。
Q Q Q次询问给定 a a a个首尾相邻的轮廓点,问这 a a a个点围成区域的权值和除以面积和是多少。
强制在线。

n , Q ≤ 2 × 1 0 5 , m ≤ 3 n − 6 , ∣ x i ∣ , ∣ y i ∣ ≤ 3 × 1 0 4 n,Q\leq 2\times 10^5,m\leq 3n-6,|x_i|,|y_i|\leq 3\times 10^4 n,Q2×105,m3n6,xi,yi3×104,询问总点数 ≤ 2 × 1 0 6 \leq 2\times 10^6 2×106

【解题思路】
由于没写过,大部分来自这个blog

平面图转对偶图,对偶图 DFS \text{DFS} DFS树的任意子树对应原图一个连通块。

询问的边圈出了对偶图的一个点集,这些就是需要求和的点,那么现在相当于将树割下若干段求和。不妨将一个点的权值设为其子树权值,对于一条被割断的对偶边,判断它是往父亲走还是往子树走,对应答案就加上或减去子树权值和。非树边可以直接忽略。

首先,对偶图最基本的需求就是把平面图中的每个块给抠出来。那么,我们该怎么抠呢?

我们显然可以将每一条无向边都变成两条有向边,这样的话围成每个块的边就不会重复且恰好把每条边用一次(最后剩下的边围出来的是无穷域),因此我们就可以通过记录每条边是否被访问来判断是否抠出来了所有块。所以,我们用邻接表存边时就可以控制一下边的标号,使得边 i i i和边 i ⊕ 1 i\oplus 1 i1互为反向边。

因为我们要保证抠出来个块中没有边,所以我们肯定要沿着块的边缘一直顺时针走或者是逆时针走。这里逆时针走(也就是每条有向边的左边是它对应的块),因为这样的话,我们直接用叉积来算块的有向面积的话得到的将是一个正数,而无穷域算出来就会是负数。这样就可以非常方便地判断无穷域。

那么,假设我们现在正在边 i i i上,那么我们下一步该往拿走呢?非常显然,我们把 i i i反向,然后顺时针旋转,碰到的第一条边就是下一条我们要走的边。如此一路走下去,当我们碰到一条已经被访问过的边,那么我们就找出了一些首尾相连的边,也就是说抠出了一个块。我们把这个块标个号,然后记录这些边属于这个块即可。

所以,我们需要给每个点连出去的边排个序。这个可以使用系统自带的 a t a n 2 atan2 atan2函数, a t a n 2 ( y , x ) atan2(y,x) atan2(y,x)将返回向量 ( x , y ) (x,y) (x,y) x x x轴正方向的夹角,范围在 ( − π , π ] (-\pi ,\pi] (π,π]中。我们可以在读入完所有的边之后,对于每个点,扫一遍所有的出边,然后按向量与 x x x轴正方向的夹角大小排个序,记录一下每条边的顺时针方向第一条是什么(记得首尾相接连成一个环)。

最后,无穷域的判定有两种方式。一种是上文讲到的判有向面积的正负,还有一种就是在抠其它块之前,先把无穷域给抠出来,这样就不会影响到其它边了。具体做法可以从左下角的一个点(最左还是最下都可以)出发,先走夹角最大的一条边,这样抠出来的块就是无穷域。所以相比第二种,第一种其实更好写。

【参考代码】(也是抄的)

#include<bits/stdc++.h>
#define pb push_back
using namespace std;

typedef long long ll;
typedef double db;
const int N=4e5+10,M=3*N;
ll s[N],s2[N];

int read()
{
	int ret=0,f=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return f?ret:-ret;
}

namespace Graph
{
	int cnt,tot,rt;
	int num[M],nex[M],fa[N];
	bool fg[M],vis[N];
	struct Point
	{
		ll x,y;
		Point(ll _x=0,ll _y=0):x(_x),y(_y){}
		Point operator + (const Point&rhs)const{return Point(x+rhs.x,y+rhs.y);}
		Point operator - (const Point&rhs)const{return Point(x-rhs.x,y-rhs.y);}
		ll operator * (const Point&rhs)const{return x*rhs.y-y*rhs.x;}
	}p[N];
	struct edge
	{
		int u,v,id;db ang;
		edge(int _u=0,int _v=0,int _d=0):u(_u),v(_v),id(_d),ang(atan2(p[v].y-p[u].y,p[v].x-p[u].x)){}
		bool operator <(const edge&rhs)const{return ang<rhs.ang;}
	}e[M];
	vector<edge>E[N],G[N];
	void ToDual()
	{
		int now,st;ll area;
		for(int i=2;i<=tot;++i) if(!num[i])
		{
			area=0;now=i;st=e[i].u;num[i]=++cnt;
			for(;;)
			{
				int tmp=nex[now];num[tmp]=cnt;
				if(e[tmp].v==st) break;
				area+=(p[e[tmp].u]-p[st])*(p[e[tmp].v]-p[st]);
				now=tmp;
			}
			s[cnt]=area;s2[cnt]=area*area;
			if(area<=0) rt=cnt;
		}
		for(int i=2;i<=tot;++i) G[num[i]].pb(edge(num[i],num[i^1],i));
	}
	void dfs(int x)
	{
		vis[x]=1;
		for(int i=0;i<(int)G[x].size();++i)
		{
			int v=G[x][i].v;
			if(vis[v]) continue;
			fa[v]=x;fg[G[x][i].id]=fg[G[x][i].id^1]=1;
			dfs(v);s[x]+=s[v];s2[x]+=s2[v]; 
		}
	}
}
using namespace Graph;


namespace DreamLolita
{
	int n,m,K;
	ll P,Q,q[N];

	ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
	int query(int x,const edge&a){return lower_bound(E[x].begin(),E[x].end(),a)-E[x].begin();}
	void solution()
	{
		n=read();m=read();K=read();tot=1;
		for(int i=1;i<=n;++i) p[i].x=read(),p[i].y=read();
		for(int i=1;i<=m;++i)
		{
			int u=read(),v=read();
			++tot;e[tot]=edge(u,v,tot);E[u].pb(e[tot]);
			++tot;e[tot]=edge(v,u,tot);E[v].pb(e[tot]);
		}
		for(int i=1;i<=n;++i) sort(E[i].begin(),E[i].end());
		for(int i=2;i<=tot;++i)
		{
			int c=query(e[i].v,e[i^1])-1;
			if(c<0) c+=E[e[i].v].size();			
			nex[i]=E[e[i].v][c].id;
		}
		ToDual();dfs(rt);
		for(int i=1;i<=K;++i)
		{
			int c=(read()+P)%n+1;
			for(int j=1;j<=c;++j) q[j]=(read()+P)%n+1; q[c+1]=q[1];
			
			P=Q=0;
			for(int j=1;j<=c;++j) 
			{
				int x=q[j],y=q[j+1],tmp=E[x][query(x,edge(x,y,0))].id;
				if(!fg[tmp]) continue;
				if(num[tmp]==fa[num[tmp^1]]) Q+=s[num[tmp^1]],P+=s2[num[tmp^1]];
				else Q-=s[num[tmp]],P-=s2[num[tmp]];
			}
			if(Q<0) P=-P,Q=-Q;
			ll d=gcd(P,Q);Q/=d;P/=d;
			if(P&1) Q<<=1; else P>>=1;
			printf("%lld %lld\n",P,Q);
		}
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("BZOJ4541.in","r",stdin);
	freopen("BZOJ4541.out","w",stdout);
#endif 
	DreamLolita::solution();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值