[Luogu P3249] [BZOJ 4541] [LOJ #2052] [HNOI2016]矿区

16 篇文章 0 订阅
11 篇文章 0 订阅
洛谷传送门
BZOJ传送门
LOJ传送门

题目描述

平面上的矿区划分成了若干个开发区域。简单地说,你可以将矿区看成一张连通的平面图,平面图划分为了若干平面块,每个平面块即为一个开发区域,平面块之间的边界必定由若干整点(坐标值为整数的点)和连接这些整点的线段组成。

每个开发区域的矿量与该开发区域的面积有关:具体而言,面积为 s ​ s​ s的开发区域的矿量为 s 2 ​ s^2​ s2。现在有 m ​ m​ m 个开采计划。每个开采计划都指定了一个由若干开发区域组成的多边形,一个开采计划的优先度被规定为矿量的总和÷开发区域的面积和;

例如,若某开采计划指定两个开发区域,面积分别为 a a a b b b,则优先度为 ( a 2 + b 2 ) / ( a + b ) (a^2+b^2)/(a+b) (a2+b2)/(a+b)。由于平面图是按照划分开发区域边界的点和边给出的,因此每个开采计划也只说明了其指定多边形的边界,并未详细指明是哪些开发区域(但很明显,只要给出了多边形的边界就可以求出是些开发区域)。

你的任务是求出每个开采计划的优先度。为了避免精度问题,你的答案必须按照分数的格式输出,即求出分子和分母,且必须是最简形式(分子和分母都为整数,而且都消除了最大公约数;例如,若矿量总和是 1.5 ​ 1.5​ 1.5,面积和是 2 ​ 2​ 2,那么分子应为 3 ​ 3​ 3,分母应为 4 ​ 4​ 4;又如,若矿量和是 2 ​ 2​ 2,面积和是 4 ​ 4​ 4,那么分子应为 1 ​ 1​ 1,分母应为 2 ​ 2​ 2)。

由于某些原因,你必须依次对每个开采计划求解(即下一个开采计划会按一定格式加密,加密的方式与上一个开采计划的答案有关)。具体的加密方式见输入格式。

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:
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
输出样例#1:
1 1 
1 2 
1 1 
9 10 
3 4

说明

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

img

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

划对应的多边形有 ( 3 + 0 ) m o d   8 + 1 = 4 (3+0) mod\ 8 +1=4 (3+0)mod 8+1=4个点,将接下的 4 4 4个数 3 , 0 , 4 , 7 3,0,4,7 3,0,4,7,分别代入 ( z i + 0 ) m o d   n + 1 (z_i+0) mod\ n + 1 (zi+0)mod n+1得到 4 4 4个点的编号为 4 , 1 , 5 , 8 4,1,5,8 4,1,5,8。计算出第一个开采计划的分子为 1 1 1,分母为 1 1 1。类似地,可计算出余下开采计划的多边形的点数和点的编号:第二个开采计划对应的多边形有 3 3 3个点,编号分别为 5 , 6 , 8 5, 6, 8 5,6,8。第三个开采计划对应的多边形有 6 6 6个点,编号分别为 1 , 2 , 6 , 5 , 8 , 4 1, 2, 6, 5, 8, 4 1,2,6,5,8,4。第四个开采计划对应的多边形有 5 5 5个点,编号分别为 1 , 2 , 6 , 8 , 4 1, 2, 6, 8, 4 1,2,6,8,4。第五个开采计划对应的多边形有 6 6 6个点,编号分别为 1 , 5 , 6 , 8 , 7 , 4 1, 5, 6, 8, 7, 4 1,5,6,8,7,4

对于100%的数据, n , k ≤ 2 ∗ 1 0 5 , m ≤ 3 n − 6 , ∣ x i ∣ , ∣ y i ∣ ≤ 3 ∗ 1 0 4 n, k \le 2*10^5, m \le 3n-6, |x_i|, |y _i| \le 3*10^4 n,k2105,m3n6,xi,yi3104。所有开采计划的 d d d之和不超过 2 ∗ 1 0 6 2*10^6 2106。保证任何开采计划都包含至少一个开发区域,且这些开发区域构成一个连通块。保证所有开发区域的矿量和不超过 2 63 − 1 2^{63}-1 2631。保证平面图中没有多余的点和边。保证数据合法。由于输入数据量较大,建议使用读入优化。

解题分析

平面图转对偶图, 然后维护子树和。

具体而言, 我们将所有边看成两条, 然后每次找到角度 &lt; &lt; <当前枚举的边的第一条边继续走, 因为每条有向边这样操作后都将属于一个单元开发区域, 这样就可以找到所有单位开发区域。 然后将无穷域视为根, 建出一棵生成树, 并记录子树和。

每次查询先找到当前边, 如果在生成树中存在这条边, 则看这条边正反哪个属于父节点开发区域, 哪个属于儿子节点开发区域。如果这条边属于儿子节点, 就说明我们围住了子树方向的区域, 答案累加子树大小; 否则说明我们不取这一部分子树, 答案减去子树大小。

总复杂度 O ( ( m + d ) l o g ( m ) ) O((m+d)log(m)) O((m+d)log(m)), 稍微有点卡常。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <vector>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 1200500
#define ll long long
template <class T>
IN void in(T &x)
{
	static bool neg; static char c;
	x = 0; c = gc;
	for (; !isdigit(c); c = gc)
	if (c == '-') neg = true;
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
	if (neg) neg = false, x = -x;
}
int n, m, plcnt, q, cnt = -1, root;
ll ans1, ans2;
struct Point {int x, y;} dot[MX];
IN Point operator + (const Point &x, const Point &y)
{return {x.x + y.x, x.y + y.y};}
IN Point operator - (const Point &x, const Point &y)
{return {x.x - y.x, x.y - y.y};}
IN ll operator * (const Point &x, const Point &y)
{return 1ll * x.x * y.y - 1ll * x.y * y.x;}
struct Edge
{
	int x, y, id;
	double rat;
	Edge (){};
	Edge (R int fr, R int to, R int ID = 0)
	{
		x = fr, y = to, id = ID;
		rat = std::atan2(dot[to].y - dot[fr].y, dot[to].x - dot[fr].x);
	}
}edge[MX];
std::vector <Edge> g[MX], h[MX];
IN bool operator < (const Edge &x, const Edge &y) {return x.rat < y.rat;}
ll area[MX], area2[MX];
bool vis[MX], on[MX];
int bel[MX], nex[MX], fat[MX], que[MX];
IN int find(R int pos, const Edge &tar)
{return std::lower_bound(g[pos].begin(), g[pos].end(), tar) - g[pos].begin();}
void Build()
{	
	int st, tmp;
	for (R int i = 0; i <= cnt; ++i)
	{
		if (bel[i]) continue;
		st = edge[i].x, bel[i] = ++plcnt; tmp = i;
		W (2333)
		{
			tmp = nex[tmp];
			bel[tmp] = plcnt;
			if (edge[tmp].y == st) break;
			area[plcnt] += (dot[edge[tmp].x] - dot[st]) * (dot[edge[tmp].y] - dot[st]);
		}
		if (area[plcnt] <= 0) root = plcnt;
		area2[plcnt] = area[plcnt] * area[plcnt];
	}
	for (R int i = 0; i <= cnt; ++i)
	h[bel[i]].push_back(Edge(bel[i], bel[i ^ 1], i));
}
void DFS(R int now)
{
	vis[now] = true;
	for (R int i = h[now].size() - 1; ~i; --i)
	{
		if (vis[h[now][i].y]) continue;
		fat[h[now][i].y] = now; on[h[now][i].id] = on[h[now][i].id ^ 1] = true;
		DFS(h[now][i].y); area[now] += area[h[now][i].y];
		area2[now] += area2[h[now][i].y];
	}
}
int main(void)
{
	freopen("mine1.in", "r", stdin);
	freopen("my.out", "w", stdout);
	int foo, bar, now, oth;
	in(n), in(m), in(q);
	for (R int i = 1; i <= n; ++i)
	in(dot[i].x), in(dot[i].y);
	for (R int i = 1; i <= m; ++i)
	{
		in(foo), in(bar);
		++cnt;
		edge[cnt] = Edge(foo, bar, cnt);
		g[foo].push_back(edge[cnt]);
		++cnt;
		edge[cnt] = Edge(bar, foo, cnt);
		g[bar].push_back(edge[cnt]);
	}
	for (R int i = 1; i <= n; ++i) std::sort(g[i].begin(), g[i].end());
	for (R int i = 0; i <= cnt; ++i)
	{
		foo = find(edge[i].y, edge[i ^ 1]) - 1;
		if (foo < 0) foo = g[edge[i].y].size() - 1;
		nex[i] = g[edge[i].y][foo].id;
	}
	Build();
	DFS(root);
	W (q--)
	{
		in(foo);
		foo = (foo + ans1) % n + 1;
		for (R int i = 1; i <= foo; ++i) in(que[i]), que[i] = (que[i] + ans1) % n + 1;
		ans1 = ans2 = 0; que[foo + 1] = que[1];
		for (R int i = 1; i <= foo; ++i)
		{
			bar = find(que[i], Edge(que[i], que[i + 1]));
			bar = g[que[i]][bar].id;
			if (!on[bar]) continue;
			now = bel[bar], oth = bel[bar ^ 1];
			if (fat[oth] == now) ans1 -= area2[oth], ans2 -= area[oth];
			else ans1 += area2[now], ans2 += area[now];
		}
		if (ans1 < 0) ans1 = -ans1, ans2 = -ans2;
		ans2 *= 2;
		ll g = std::__gcd(ans1, ans2);
		printf("%lld %lld\n", ans1 /= g, ans2 /= g);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值