BZOJ4541 [Hnoi2016]矿区

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

 

 

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

 

Description

  平面上的矿区划分成了若干个开发区域。简单地说,你可以将矿区看成一张连通的平面图,平面图划分为了若
干平面块,每个平面块即为一个开发区域,平面块之间的边界必定由若干整点(坐标值为整数的点)和连接这些整点
的线段组成。每个开发区域的矿量与该开发区域的面积有关:具体而言,面积为s的开发区域的矿量为 s^2。现在
有 m 个开采计划。每个开采计划都指定了一个由若干开发区域组成的多边形,一个开采计划的优先度被规定为矿
量的总和÷开发区域的面积和;例如,若某开采计划指定两个开发区域,面积分别为 a和b,则优先度为(a^2+b^2)
/(a+b)。由于平面图是按照划分开发区域边界的点和边给出的,因此每个开采计划也只说明了其指定多边形的边界
,并未详细指明是哪些开发区域(但很明显,只要给出了多边形的边界就可以求出是些开发区域)。你的任务是求
出每个开采计划的优先度。为了避免精度问题,你的答案必须按照分数的格式输出,即求出分子和分母,且必须是
最简形式(分子和分母都为整数,而且都消除了最大公约数;例如,若矿量总和是 1.5,面积和是2,那么分子应
为3,分母应为4;又如,若矿量和是 2,面积和是 4,那么分子应为 1,分母应为 2)。由于某些原因,你必须依
次对每个开采计划求解(即下一个开采计划会按一定格式加密,加密的方式与上一个开采计划的答案有关)。具体
的加密方式见输入格式。

Input

  第一行三个正整数 n,m,k,分别描述平面图中的点和边,以及开采计划的个数。接下来n行,第 i行(i=1,2,…
,n)有两个整数x_i, y_i,  表示点i的坐标为(x_i, y_i)。接下来m行,第 i行有两个正整数a,b,表示点a和b 之间
有一条边。接下来一行若干个整数,依次描述每个开采计划。每个开采计划的第一个数c指出该开采计划由开发区
域组成的多边形边界上的点的个数为d=(c+P) mod n + 1;接下来d个整数,按逆时针方向描述边界上的每一个点:
设其中第i个数为z_i,则第i个点的编号为(z_i+P) mod n + 1。其中P 是上一个开采计划的答案中分子的值;对于
第 1 个开采计划,P=0。

Output

  对于每个开采计划,输出一行两个正整数,分别描述分子和分母。

Sample Input

9 14 5
0 0
1 0
2 0
0 1
1 1
2 1
0 2
1 2
2 2
1 2
2 3
5 6
7 8
8 9
1 4
4 7
5 8
3 6
6 9
4 8
1 5
2 6
6 8
3 3 0 4 7 1 3 4 6 4 8 0 4 3 6 2 3 8 0 4 6 2 5 0 4 5 7 6 3

Sample Output

1 1
1 2
1 1
9 10
3 4

HINT

 

输入文件给出的9个点和14条边描述的平面图如下所示:



第一个开采计划,输入的第1个值为3,所以该开采计

划对应的多边形有(3+0) mod 8 +1=4个点,将接下的4个数3,0,4,7,分别代入(z_i+0) mod n + 1得到4个点的编号

为4,1,5,8。计算出第一个开采计划的分子为1,分母为1。类似地,可计算出余下开采计划的多边形的点数和点的

编号:第二个开采计划对应的多边形有3个点,编号分别为5, 6, 8。第三个开采计划对应的多边形有6个点,编号

分别为1, 2, 6, 5, 8, 4。第四个开采计划对应的多边形有5个点,编号分别为1, 2, 6, 8, 4。第五个开采计划对

应的多边形有6个点,编号分别为1, 5, 6, 8, 7, 4。

对于100%的数据,n, k ≤ 2×10^5, m ≤ 3n-6, |x_i|, |y

_i| ≤ 3×10^4。所有开采计划的d之和不超过2×10^6。保证任何开采计划都包含至少一个开发区域,且这些开发

区域构成一个连通块。保证所有开发区域的矿量和不超过 2^63-1。保证平面图中没有多余的点和边。保证数据合

法。由于输入数据量较大,建议使用读入优化。

 

 

正解:平面图转对偶图+树上统计

解题报告:

  这是我做的第一道计算几何大神题呀…

  考虑先把原图转成对偶图,转的方式就是,把无向边拆成两条有向边,然后对于每个点把他的所有出边极角排序,用vector的话很方便。

  然后我们就可以对于每条边算出从他出发的下一条要走的边是哪一条,二分预处理一下,记录下来。

  接下来就是扣域了!

  我从每一条没有归属的边出发,一路往下走,直到回到这条边的起点,把每条边标记为同一个域,我们可以在图上画画就能发现,扣出来的一定是这些边围出来的域。

  至于每次的下一条边,是极角序变大还是变小,就看你的定义了,其实是等价的。

  需要注意的是,我要用叉积顺便算一下每个域的面积,而面积为负数的域就是无穷域。

  然后我令无穷域为根节点,dfs一遍所有域,得到一棵对偶图的生成树,标记一下树边,并且统计子树的面积和以及面积平方和。

  对于每次询问,我只需要用像最开始找下一条边的方式找出相邻两个点之间的边的编号,如果是非树边就直接忽略,否则计算一下这条边所代表的儿子节点中的子树贡献。

  我们不妨规定一个方向,如果这条边时正方向就加上贡献,否则就减去,当然最后要取绝对值。

  

  我也不会证明呀...似乎可以用反证法来考虑,感觉一下还是可以意会的...

 

  有一个小trick:考虑我们叉积算出来的面积是四边形面积,本应该要/2,但是有可能会出现小数,所以我们先上下同时乘4就可以避免这个问题了...

 

 

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int MAXN = 200011;
const int MAXM = 1200011;
int n,m,k,ecnt,next[MAXM],rt,belong[MAXM],cnt,father[MAXM]/*!!!*/,ask[MAXN];
bool vis[MAXM],in[MAXM];
LL s[MAXM],sp[MAXM],P,Q;
struct point{
	int x,y;
	inline point operator - (const point a) const { return (point){x-a.x,y-a.y}; }//向量,末-初
	inline LL operator * (const point a) const { return 1LL*x*a.y-1LL*y*a.x; }//叉积
}p[MAXM];

struct edge{
	int u,v,id;
	double ang;
	edge(){}
	edge(int a,int b,int ii){ u=a; v=b; id=ii; ang=atan2(p[b].y-p[a].y,p[b].x-p[a].x); }
	inline bool operator < (const edge a) const { return a.ang>ang; }
}e[MAXM];

vector<edge>w[MAXN],Tree[MAXM];

inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline void link(int x,int y){ ecnt++; e[ecnt]=edge(x,y,ecnt); w[x].push_back(e[ecnt]); }

inline LL gcd(LL x,LL y){ if(y==0) return x; return gcd(y,x%y); }

inline int find(int x,const edge &bian){
	int l=0,r=w[x].size()-1,mid;
	while(l<r) {
		mid=(l+r+1)>>1;/*!!!二分的方式*/
		if(bian<w[x][mid]) r=mid-1;
		else l=mid;
	}
	return l;
}

inline void dfs(int x){
	vis[x]=1;
	sp[x]=s[x]*s[x]; s[x]<<=1;//考虑我们叉积算出来的面积是四边形面积,本应该要/2,但是有可能会出现小数,所以我们先上下同时乘4就可以避免这个问题了
	for(int i=0,ss=Tree[x].size();i<ss;i++) {
		int v=Tree[x][i].v; if(vis[v]) continue;
		father[v]=x; in[Tree[x][i].id]=in[Tree[x][i].id^1]=1;//标记为树边
		dfs(v); s[x]+=s[v]; sp[x]+=sp[v];//统计子树面积和,以及子树面积的平方和
	}
}

inline void work() {
	n=getint(); m=getint(); k=getint();
	ecnt=1; int x,y,nex;
	for(int i=1;i<=n;i++) x=getint(),y=getint(),p[i]=(point){x,y};
	for(int i=1;i<=m;i++) x=getint(),y=getint(),link(x,y),link(y,x);
	for(int i=1;i<=n;i++) sort(w[i].begin(),w[i].end());//把每个点的出边按极角排序
	for(int i=2;i<=ecnt;i++) {//预处理出每条边的反向边的极角序的下一条边,即走完这条边之后要走的下一条边
		nex=find(e[i].v,e[i^1])-1;
		if(nex==-1) nex=w[e[i].v].size()-1;//转一圈转了回来
		next[i]=w[e[i].v][nex].id;//这条边走出去的下一条边
	}

	for(int i=2;i<=ecnt;i++) {//处理每条边属于哪个域
		if(!belong[i]) {
			belong[i]=belong[next[i]]=++cnt;
			for(int now=next[i];e[now].v!=e[i].u;now=next[now],belong[now]=cnt) {//直到回到原点
				s[cnt]+=(p[e[now].u]-p[e[i].u])*(p[e[now].v]-p[e[i].u]);//叉积算面积,注意后者位于前者的逆时针方向才为正
			}
			if(s[cnt]<=0) rt=cnt;//无穷域
		}
	}

	for(int i=2;i<=ecnt;i++) Tree[belong[i]].push_back(edge(belong[i],belong[i^1],i));
	dfs(rt); int d,now; P=Q=0;
	while(k--) {
		d=(getint()+P)%n+1;
		for(int i=1;i<=d;i++) ask[i]=(getint()+P)%n+1;
		ask[d+1]=ask[1]; P=Q=0;
		for(int i=1;i<=d;i++) {
			now=w[ask[i]][ find(ask[i],edge(ask[i],ask[i+1],0)) ].id;//找到当前边中的下一条边
			if(!in[now]) continue;//考虑这条边在对偶图中的对应边,如果是非树边则不用考虑
			if(father[belong[now]] == belong[now^1]) Q+=s[belong[now]],P+=sp[belong[now]];//考虑子树的统计,规定方向后,则只需对子树进行加加减减操作即可
			else Q-=s[belong[now^1]],P-=sp[belong[now^1]];
		}
		LL gg=gcd(P,Q); 
		//LL gg=__gcd(P,Q);
		P/=gg; Q/=gg;
		printf("%lld %lld\n",P,Q);
	}
	//cout<<clock()<<endl;
}

int main()
{
    work();
    return 0;
}

  

posted @ 2017-02-21 11:47  ljh_2000  阅读(835)  评论(0编辑  收藏  举报