4541: [Hnoi2016]矿区

4541: [Hnoi2016]矿区

Time Limit: 30 Sec   Memory Limit: 512 MB
Submit: 337   Solved: 139
[ Submit][ Status][ Discuss]

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。保证平面图中没有多余的点和边。保证数据合

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

Source

[ Submit][ Status][ Discuss]



第一次看到这题以为单纯叉积就解决了= =  惨。。其实并不是

只有分母的计算能够叉积。。。。。。。。。。因为分子是平方和

既然给出的是一张平面图,那么考虑它的对偶图。

无穷域的话,不再划分,直接当做一个面0

从0出发,在对偶图中dfs,得到一棵dfs树

每个节点对应原图中的一个面,维护它的面积,面积平方

dfs过程中,对这两个值求一个子树权值和

对于每次询问给出的那个连通块,找到每条边对应在dfs树中的树边,如果是非树边则忽略

这条边如果指向块内的某个面i,答案加上i的子树权值和

如果指向块外的某个面i,答案减去i的子树权值和

画个图很容易得出正确性

每个块在dfs树中的树边,代表进块的和代表出块的边数要么相等要么前者比后者多一

如果一条树边代表进块,我们加上这个权值,倘若后续有不属于当前块的面

那么一定能找到一条出边,把多余的抵消

如果减得多了,后续肯定还有进块边,能够把多减的加回来



至于任意平面图如何转换成对偶图传送门

留点笔记。。。。。。。。。。。。。

对于原图每条无向边,拆成两条有向边,方便起见,i与i^1互为反向边

每个点连出去的所有边按照向量极角序排好

这样预处理从一个点出发的每条边,它顺时针方向的下一条边是谁,记为nex

每次任选一条没有访问过的边,不断走其反向边的nex,直到走回来

这样一定围出一个域

顺便用叉积算下面积

因为多边形按照逆时针计算有向面积为正,顺时针为负

所以算出来为负的都说明我们围到了无穷域= =跳过即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int maxn = 2E5 + 20;
const int N = maxn*2;
const int maxm = maxn*6;
typedef double DB;
typedef long long LL;

struct Point{
	int x,y; Point(){}
	Point(int x,int y): x(x),y(y){}
	Point operator - (const Point &B) {return Point(x - B.x,y - B.y);}
}O,Node[maxn];
typedef Point Vector;

LL Cross(Vector v1,Vector v2) {return 1LL*v1.x*v2.y - 1LL*v2.x*v1.y;}

struct E{
	int to; DB ang; Vector v; E(){}
	E(int to,Vector v): to(to),v(v){ang = atan2(v.y,v.x);}
}edgs[maxm];

struct E2{
	int x,y,num; E2(){}
	E2(int x,int y,int num): x(x),y(y),num(num){}
	bool operator < (const E2 &B) const
	{
		if (x < B.x) return 1;
		if (x > B.x) return 0;
		return y < B.y;
	}
	bool operator == (const E2 &B) const
	{
		return x == B.x && y == B.y;
	}
}e2[maxm];

int n,m,k,cnt,nex[maxm],bel[maxm],typ[maxm],A[maxn],id[maxm];
LL P,s2[N],sum[N];
bool bo[maxm],vis[N];

vector <int> v[maxn],G[N];

LL gcd(const LL &x,const LL &y) {return !y?x:gcd(y,x%y);}
bool cmp(const int &x,const int &y) {return edgs[x].ang < edgs[y].ang;}
int Unlock(const LL &x) {return (x + P) % n + 1;}

void Build(int g)
{
	int tail; LL tot = 0; id[tail = 1] = g;
	for (int z = nex[g^1]; z != g; z = nex[z^1]) id[++tail] = z;
	for (int i = 1; i < tail; i++) 
		tot += Cross(Node[edgs[id[i]].to] - O,Node[edgs[id[i+1]].to] - O);
	tot += Cross(Node[edgs[id[tail]].to] - O,Node[edgs[id[1]].to] - O);
	if (tot <= 0) return;
	sum[++cnt] = 2LL*tot; s2[cnt] = tot*tot;
	for (int i = 1; i <= tail; i++) bel[id[i]] = cnt,bo[id[i]] = 1; 
}

void Dfs(int x,int fa)
{
	for (int i = 0; i < G[x].size(); i++)
	{
		int e = G[x][i];
		if (bel[e^1] == fa || vis[bel[e^1]]) continue;
		typ[e] = 1; typ[e^1] = 0;
		vis[bel[e^1]] = 1; Dfs(bel[e^1],x);
		sum[x] += sum[bel[e^1]];
		s2[x] += s2[bel[e^1]];
	}
}

int Get_e2(E2 e)
{
	int L = 2,R = (m<<1|1);
	while (R - L > 1)
	{
		int mid = (L + R) >> 1;
		if (e2[mid] == e) return e2[mid].num;
		else if (e2[mid] < e) L = mid;
		else R = mid;
	}
	if (e2[L] == e) return e2[L].num;
	else return e2[R].num;
}

int getint()
{
	char ch = getchar();
	int ret = 0,g = 1;
	while (ch < '0' || '9' < ch)
	{
		if (ch == '-') g = -1;
		ch = getchar();
	} 
	while ('0' <= ch && ch <= '9')
		ret = ret*10 + ch - '0',ch = getchar();
	return ret*g;
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	n = getint(); m = getint(); k = getint(); O = Point(233,233);
	for (int i = 1; i <= n; i++) 
	{
		int x = getint(),y = getint();
		Node[i] = Point(x,y);
	}
	for (int i = 1; i <= m; i++)
	{
		int x = getint(),y = getint();
		edgs[i<<1] = E(y,Point(Node[y].x - Node[x].x,Node[y].y - Node[x].y));
		e2[i<<1] = E2(x,y,i<<1);
		edgs[i<<1|1] = E(x,Point(Node[x].x - Node[y].x,Node[x].y - Node[y].y));
		e2[i<<1|1] = E2(y,x,i<<1|1);
		v[x].push_back(i<<1);
		v[y].push_back(i<<1|1);
	}
	sort(e2 + 2,e2 + (m<<1|1) + 1);
	for (int i = 1; i <= n; i++) 
	{
		sort(v[i].begin(),v[i].end(),cmp);
		for (int j = v[i].size() - 1; j; j--)
		{
			int X = v[i][j],Y = v[i][j-1];
			nex[v[i][j]] = v[i][j-1];
		}
		nex[v[i][0]] = v[i][v[i].size()-1];
	}
	for (int i = 2; i <= (m<<1|1); i++)
		if (!bo[i]) Build(i);
	for (int i = 2; i <= (m<<1|1); i++)
		if (bel[i] != bel[i^1])
			G[bel[i]].push_back(i);
	memset(typ,-1,sizeof(typ));
	vis[0] = 1; Dfs(0,-1);
	while (k--)
	{
		int d = getint(); LL X,Y; X = Y = 0;
		d = Unlock(d);
		for (int i = 1; i <= d; i++)
			A[i] = getint(),A[i] = Unlock(A[i]);
		A[d+1] = A[1];
		for (int i = 1; i <= d; i++)
		{
			int t = Get_e2(E2(A[i],A[i+1],0));
			if (typ[t] == -1) continue;
			if (typ[t]) X -= s2[bel[t^1]],Y -= sum[bel[t^1]];
			else X += s2[bel[t]],Y += sum[bel[t]];
		}
		LL GCD = gcd(X,Y);
		X /= GCD; Y /= GCD;
		printf("%lld %lld\n",X,Y);
		P = X;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值