洛谷传送门
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条边描述的平面图如下所示:
第一个开采计划,输入的第 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,k≤2∗105,m≤3n−6,∣xi∣,∣yi∣≤3∗104。所有开采计划的 d d d之和不超过 2 ∗ 1 0 6 2*10^6 2∗106。保证任何开采计划都包含至少一个开发区域,且这些开发区域构成一个连通块。保证所有开发区域的矿量和不超过 2 63 − 1 2^{63}-1 263−1。保证平面图中没有多余的点和边。保证数据合法。由于输入数据量较大,建议使用读入优化。
解题分析
平面图转对偶图, 然后维护子树和。
具体而言, 我们将所有边看成两条, 然后每次找到角度 < < <当前枚举的边的第一条边继续走, 因为每条有向边这样操作后都将属于一个单元开发区域, 这样就可以找到所有单位开发区域。 然后将无穷域视为根, 建出一棵生成树, 并记录子树和。
每次查询先找到当前边, 如果在生成树中存在这条边, 则看这条边正反哪个属于父节点开发区域, 哪个属于儿子节点开发区域。如果这条边属于儿子节点, 就说明我们围住了子树方向的区域, 答案累加子树大小; 否则说明我们不取这一部分子树, 答案减去子树大小。
总复杂度 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);
}
}