• BZOJ4541: [Hnoi2016]矿区


    BZOJ4541: [Hnoi2016]矿区

    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。

    保证平面图中没有多余的点和边。保证数据合法。

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


    题解Here!

    本蒟蒻肝了两晚上才肝完,肝都快没了。。。
    首先,要把平面图转成对偶图。
    对偶图应该都知道吧。。。
    就是将平面图中所有的面变成点,点变成面,边“旋转90度”后得到的图。
    不知道去看这题:BZOJ1001: [BeiJing2006]狼抓兔子
    虽然我没有写对偶图的代码,不过自己$YY$一下应该就能把代码写出来。
    如何转对偶图,关键就是如何划分原图中的面。
    这个方法是,双向边先看成两条单向边,这样每条边都属于一个面。
    然后将以每一个点为起点的边极角排序。
    对于一条边$(s,t)$,我们在以$t$为起点的边中找到$(t,s)$。
    排序后其上一条边就是当前面的下一条边界。
    这样一直找到整个区域闭合,就说明把这个面上的边全部找出来了。
    这个步骤可以利用$STL$中的$vector$轻松做到。
    每条边连接两个面,即它所在的面和它的反向边所在的面,便建好了对偶图。
    之后在这个对偶图中随意地拿出一棵生成树,以无界域,即原图中外围无限的面,为根。
    但是如何找到无界域呢?
    我们发现,利用叉积算有向面积的时候,算出来是负数的就是无界域。
    然后标记所有树边,记录生成树上每棵子树中的矿区面积和及面积平方和。
    对于一个询问,先找到这个询问里出现的边,这个应该不用多说。
    然后分类讨论:
    1. 这条边是非树边,忽略。
    2. 这条边所在的面是儿子,就加上子树的面积。
    3. 这条边所在的面是父亲,就减去儿子子树的面积。
    这个分类讨论建议手动画个图感性理解一下。。。
    附上代码(反正乱七八糟的一大堆稀奇古怪的变量名+各种各样的迭代器我也很无奈啊。。。):
    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cmath>
    #include<vector>
    #define MAXN 200010
    #define MAXM 1200010
    #define eps (1e-10)
    using namespace std;
    int n,m,q,num=1,top=0,root;
    int pos[MAXM],after[MAXM],fa[MAXM],que[MAXM];
    long long area[MAXM],sum[MAXM];
    bool vis[MAXM],turn[MAXM];
    struct Point{
    	long long x,y;
    	friend Point operator -(const Point p,const Point q){return (Point){p.x-q.x,p.y-q.y};}
    	friend long long operator *(const Point p,const Point q){return 1LL*p.x*q.y-p.y*q.x;}
    }point[MAXN];
    struct Edge{
    	int u,v,id;
    	double w;
    	friend bool operator <(const Edge p,const Edge q){
    		if(fabs(p.w-q.w)<eps)return p.v<q.v;
    		return p.w<q.w;
    	}
    }edge[MAXM];
    vector<Edge> head[MAXN],last[MAXM];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    long long gcd(long long x,long long y){
    	if(!y)return x;
    	return gcd(y,x%y);
    }
    inline void add_edge(int x,int y){
    	num++;
    	edge[num].u=x;edge[num].v=y;edge[num].id=num;
    	edge[num].w=atan2(point[y].y-point[x].y,point[y].x-point[x].x);
    	head[x].push_back(edge[num]);
    }
    void build(){
    	for(int i=1;i<=n;i++)sort(head[i].begin(),head[i].end());
    	for(int i=2;i<=num;i++){
    		int v=edge[i].v;
    		vector<Edge>::iterator k=lower_bound(head[v].begin(),head[v].end(),edge[i^1]);
    		if(k==head[v].begin())k=head[v].end();
    		k--;
    		after[i]=(*k).id;
    	}
    	for(int i=2;i<=num;i++){
    		if(pos[i])continue;
    		pos[i]=pos[after[i]]=++top;
    		for(int j=after[i];edge[j].v!=edge[i].u;j=after[j],pos[j]=top)
    			area[top]+=((point[edge[j].u]-point[edge[i].u])*(point[edge[j].v]-point[edge[i].u]));
    		if(area[top]<=0)root=top;
    	}
    	for(int i=2;i<=num;i++)last[pos[i]].push_back((Edge){pos[i],pos[i^1],i,0});
    }
    void dfs(int rt,int f){
    	fa[rt]=f;
    	sum[rt]=area[rt]*area[rt];
    	area[rt]<<=1;//叉积算面积后应该除以2,但是为了避免小数,所以分子分母同时乘4
    	vis[rt]=true;
    	for(int i=0;i<last[rt].size();i++){
    		int v=last[rt][i].v;
    		if(vis[v])continue;
    		turn[last[rt][i].id]=turn[last[rt][i].id^1]=true;
    		dfs(v,rt);
    		area[rt]+=area[v];
    		sum[rt]+=sum[v];
    	}
    }
    void work(){
    	long long ans1=0,ans2;
    	while(q--){
    		int k=(read()+ans1)%n+1;
    		for(int i=1;i<=k;i++)que[i]=(read()+ans1)%n+1;
    		que[k+1]=que[1];
    		ans1=ans2=0;
    		for(int i=1,x,y,id;i<=k;i++){
    			x=que[i];y=que[i+1];
    			Edge line=(Edge){x,y,0,atan2(point[y].y-point[x].y,point[y].x-point[x].x)};
    			vector<Edge>::iterator v=lower_bound(head[x].begin(),head[x].end(),line);
    			id=(*v).id;
    			if(!turn[id])continue;
    			if(fa[pos[id]]==pos[id^1]){
    				ans1+=sum[pos[id]];
    				ans2+=area[pos[id]];
    			}
    			else{
    				ans1-=sum[pos[id^1]];
    				ans2-=area[pos[id^1]];
    			}
    		}
    		long long t=gcd(ans1,ans2);
    		ans1/=t;ans2/=t;
    		printf("%lld %lld
    ",ans1,ans2);
    	}
    }
    void init(){
    	int x,y;
    	n=read();m=read();q=read();
    	for(int i=1;i<=n;i++){point[i].x=read();point[i].y=read();}
    	for(int i=1;i<=m;i++){
    		x=read();y=read();
    		add_edge(x,y);add_edge(y,x);
    	}
    	build();
    	dfs(root,0);
    }
    int main(){
    	init();
    	work();
        return 0;
    }
    
  • 相关阅读:
    重学Java 面向对象 之 final
    java并发学习04---Future模式
    java并发学习03---CountDownLatch 和 CyclicBarrier
    java并发学习02---ReadWriteLock 读写锁
    java并发学习01 --- Reentrantlock 和 Condition
    链表的倒数第k个节点
    重建二叉树
    java并发学习--线程池(一)
    二叉树的深度
    vue-常用指令(v-for)
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9704337.html
Copyright © 2020-2023  润新知