Tag Archives: 计算几何

LeetCode Range Addition II

LeetCode Range Addition II Given an m * n matrix M initialized with all 0‘s and several update operations. Operations are represented by a 2D array, and each operation is represented by an array with two positive integers a and b, which means M[i][j] should be added by one for all 0 <= i < a and 0 <= j < b. You need to count and return the number of maximum integers in the matrix after performing all the operations. Example 1:

Input:
m = 3, n = 3
operations = [[2,2],[3,3]]
Output: 4
Explanation:
Initially, M =
[[0, 0, 0],
 [0, 0, 0],
 [0, 0, 0]]
After performing [2,2], M =
[[1, 1, 0],
 [1, 1, 0],
 [0, 0, 0]]
After performing [3,3], M =
[[2, 2, 1],
 [2, 2, 1],
 [1, 1, 1]]
So the maximum integer in M is 2, and there are four of it in M. So return 4.
Note:
  1. The range of m and n is [1,40000].
  2. The range of a is [1,m], and the range of b is [1,n].
  3. The range of operations size won’t exceed 10,000.

给定一个全为0的矩阵M,和一系列的操作,每个操作都是一对(a,b),表示要对所有i在[0,a)和j在[0,b)的元素M[i][j],问最终矩阵M中最大元素的个数。 这个题稍微想一下就会发现非常简单。 假设矩阵的左下角坐标为(0,0),对于某一个操作(a,b),表示把以(0,0)和(a,b)为对角顶点的子矩阵元素加1。那么(a1,b1)和(a2,b2)两个操作的重叠区域(0,0)-(a2,b1)所在子矩阵的元素就被加了两次,他们的值最大且相同。 所以我们只需要遍历所有操作,分别找到行和列的最小值,则他们和原点围成的子矩阵的元素值最大,且相等。 代码非常简单,如下: [cpp] class Solution { public: int maxCount(int m, int n, vector<vector<int>>& ops) { int minRow = m, minCol = n; for (const auto &op : ops) { minRow = min(minRow, op[0]); minCol = min(minCol, op[1]); } return minRow*minCol; } }; [/cpp] 本代码提交AC,用时6MS。 ]]>

hihoCoder 1508-剑刃风暴

hihoCoder 1508-剑刃风暴

#1508 : 剑刃风暴

时间限制:20000ms
单点时限:2000ms
内存限制:256MB

描述

主宰尤涅若拥有一招非常厉害的招式——剑刃风暴,“无论是战士还是法师,都害怕尤涅若的武士刀剑技”。 现在战场上有N名敌对英雄,他们的位置分别为(Xi, Yi),而剑刃风暴的伤害范围是一个半径为R的圆形,尤涅若可以选择一个坐标作为剑刃风暴的中心,所有处于这个圆形范围内的英雄都会受到剑刃风暴的伤害。 现在尤涅若想要知道,他的剑刃风暴最多可以同时伤害到多少敌对英雄。

输入

第一行为两个整数N和R,分别敌对英雄的数量以及剑刃风暴的半径。 接下来的N行,每行两个整数Xi和Yi,描述一个英雄的坐标。 对于30%的数据,满足1<=N<=10 对于60%的数据,满足1<=N<=100 对于100%的数据,满足1<=N<=2000, 0<=Xi, Yi<=108, 1<=R<=108,可能有两名英雄的坐标是相同的。

输出

输出一行Ans,表示尤涅若的剑刃风暴最多能够伤害到的英雄数量。
样例输入
10 2
0 10
0 10
9 10
1 2
4 5
8 8
8 4
4 2
7 7
0 7
样例输出
3

简化题意:给定平面上n个点,和一个半径为r的圆,这个圆可以任意放置,问最多能覆盖多少个点。 这题类似poj1981(单位圆),我是参考这个题解来做的。 暴力方法。最优的圆肯定是能覆盖最多的点,且有些点落在圆周上,导致这个圆只要稍微往旁边移一下,覆盖的点就会减少。所以枚举任意两个点,假设这两个点正好落在最优圆的圆周上,则由此可以确定一个圆心,然后统计所有落在这个圆内的点的个数,并更新最大值。时间复杂度是$$O(n^3)$$。 有一点需要注意的是两个圆周上的点,如果它们的距离不是刚好等于直径,则能确定两个圆心,但只需算一个就好了。比如有三个点A,B,C确定的最优圆是下图中的实线,如果我们在由两点确定圆心的过程中,始终统一确定“下边”的圆,则虽然A,B确定的圆是虚线的(虽然A,B确定的圆还可以是实线,但根据我们的规定,统一取“下边”的圆),不是最优。但当遍历到A,C点对时,根据规则,这两个点确定的圆是实线的,也就是说,只要根据统一的规则确定圆心,肯定能找到那个最优圆的。 已知两点求圆心的方法请看这篇博客中的GetCenter函数,下图作了一些辅助线帮助理解。关于atan和atan2的区别,请看这篇博客 暴力方法在hiho上TLE,需要寻求更高效的算法。 假设以两个点i,j为圆心画半径为R的圆,如果两个圆相交,则在相交的弧上任意一点画圆,都能覆盖原来的i,j两点。基于这个思想,假设现在固定点i为圆心,R为半径画圆,求出所有以其他点为圆心画的圆和圆i相交的弧,则在相交重叠次数最多的那段弧上的点为圆心画的圆肯定能覆盖最多的点,覆盖的点数就是这段弧被重叠的次数。每个点i都能求出这样一个最大值,则所有点的这个最大值的最大值就是全局能覆盖的最多点。 怎样表示两个圆相交的那段弧呢,使用极坐标,因为弧是由两条半径张成的,所以可以表示成两条半径的极坐标夹角,并且标明夹角的开始(is=1)和结束(is=-1)。求重叠次数最多的弧的方法是,把所有重叠弧的起止半径的极坐标夹角排个序,然后遍历一下,当夹角开始(is=1)次数最多且结束(is=-1)次数最少时,说明重叠次数最多,所以就是is的累加和的最大值。 完整代码借鉴这篇博客,注意原代码是针对R=1的情况,如果是任意的R,则计算角差那里还需除以半径R。完整代码如下: [cpp] #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <iostream> using namespace std; const int MAX_N = 2005; const double PI = acos(-1.0); //const double R = 1.0;//定义覆盖圆半径为R int T, n, total, R; struct Point { double x, y; }point[MAX_N]; struct Angle { double data; int is; bool operator < (const Angle& rhs) const { return data<rhs.data; } }angle[MAX_N * 2]; inline double Dis(Point a, Point b)//计算线段ab的长度 { return sqrt((a.x – b.x)*(a.x – b.x) + (a.y – b.y)*(a.y – b.y)); } inline double God(Point a, Point b)//计算向量ab的极角 { double res = atan(fabs((b.y – a.y) / (b.x – a.x))); if (b.y<a.y) { if (b.x<a.x) res += PI; else res = 2 * PI – res; } else { if (b.x<a.x) res = PI – res; } return res; } void solve() { int res = 1; for (int i = 0; i<n; i++) { total = 0; for (int j = 0; j<n; j++) { if (i == j) continue; double dist = Dis(point[i], point[j]); if (dist>2.0 * R) continue; double base = God(point[i], point[j]); double extra = acos((dist / 2.0) / R); //计算差角 angle[total].data = base – extra; // in angle[total++].is = 1; angle[total].data = base + extra; // out angle[total++].is = -1; } if (total <= res) continue; sort(angle, angle + total); int tmp = 1; for (int j = 0; j<total; j++) { tmp += angle[j].is; res = max(res, tmp); } } printf("%d\n", res); } int main() { //freopen("input.txt", "r", stdin); scanf("%d %d", &n, &R); for (int i = 0; i<n; i++) { scanf("%lf %lf", &point[i].x, &point[i].y); } solve(); return 0; } [/cpp] 本代码提交AC,用时1345MS。 平时几乎没做过计算几何的题目,发现好难呀。 ]]>

hihoCoder 1495-矩形分割

hihoCoder 1495-矩形分割

#1495 : 矩形分割

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

小Hi有一块由NxM个单位正方形组成的矩形。现在小Ho在某些单位正方形上画了一道分割线,这条分割线或者是单位正方形的主对角线(用’\’表示),或者是副对角线(用’/’表示)。 现在小Hi想知道这些分割线把NxM的矩形分割成了多少块区域。 例如
/\
\/
就把2×2的矩形分成了5个区域。
/\/\
\  /
 \/\
把3×4的矩形分成了7个区域。

输入

第一包含两个整数N和M。(1 <= N, M <= 100) 以下N行每行包含M个字符。每个字符只可能是/\或者空格。

输出

分割成的区域数目。
样例输入
3 4
/\/\
\  /
 \/\
样例输出
7

本题又是一个考察并查集的题,再一次栽在了并查集! 给定一个由n*m个小正方形组成的大矩形,现在在某些小正方形上画一道分隔线,该分隔线可能是小正方形的主对角线\,也可能是副对角线/。问经过分隔之后,把大矩形分隔成了多少块区域。 判断平面区域个数问题,一般都可以转化为并查集的问题,但是这题相对上一次的并查集又进了一步,这题应该算是三维的并查集了。 对于小正方形的每个点,我们用三维信息来表示(x,y,z),其中x,y表示顶点所在小正方形的行号和列号,z表示该顶点在该小正方形中的位置,坐标从左上角开始顺时针递增,如下图。
0-----1
|     |
|     |
3-----2
初始时,每个顶点自成一体,所以par[i]=i。 但是因为画的分隔线只能是对角线,所以相邻两个正方形中的共享边肯定属于同一个区域,应该把相邻正方形共享边的两个顶点union起来。比如下图中,第一列的两个正方形,蓝色的两条边应该union,即左上角正方形的2号节点应该和左下角正方形的0号节点union;同理,第一行的两个正方形,红色的两条边也应该union,即左上角的1号节点和右上角的3号节点union。 其实,红色的边也可以union 0和2,但此时蓝色的边就应该union 1和3了,只要两者不一致并在整个程序中统一就行。
0-----1  0-----1
|     |  |     |
|     |  |     |
3-----2  3-----2
0-----1
|     |
|     |
3-----2
注意,不是把红色边的0和1 union,2和3 union,因为0和1可能属于不同的区域,比如下图中,红色的0和1就属于不同的区域了。
0-----1  0-----1
|   / |  | \   |
| /   |  |   \ |
3-----2  3-----2
0-----1
|     |
|     |
3-----2
初始化完成之后,就需要进行并查集操作了。如果遇到空格,好说,把这个正方形的四个顶点都union起来。 如果遇到左斜杠/,类似于上图左上角的分隔线,则把这个正方形中的四个顶点分成了两个不同的区域。此时,我们需要统一一个划分顶点的规则:被分隔的对角点和其顺时针的下一个顶点属于一个集合。比如3顺时针的下一个顶点是0,所以3和0 union;1顺时针的下一个顶点是2,所以1和2 union。 类似的,如果遇到右斜杠\,类似上图右上角的分隔线。根据统一的规则,0和1 union,2和3 union。 所有操作完成之后,统计一下并查集中不同区域的个数就是最终结果。 代码如下,注意%c会读取换行符,所以在每行开头先%c读取上一行的换行符。 [cpp] #include<iostream> #include<cstdio> #include<unordered_map> #include<algorithm> #include<functional> #include<vector> using namespace std; const int MAXN = 105; int n, m; vector<int> par(MAXN * MAXN * 4); int find_par(int u) { if (par[u] != u) par[u] = find_par(par[u]); return par[u]; } void union_par(int u, int v) { par[find_par(u)] = find_par(v); } int id(int x, int y, int z) { return x * m * 4 + y * 4 + z; } int main() { //freopen("input.txt", "r", stdin); scanf("%d %d", &n, &m); int total = n * m * 4; for (int i = 0; i < total; ++i)par[i] = i; for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { if (i > 0)union_par(id(i, j, 0), id(i – 1, j, 2)); if (j > 0)union_par(id(i, j, 3), id(i, j – 1, 1)); //if (i < n – 1)union_par(id(i, j, 2), id(i + 1, j, 0)); // 多余 //if (j < m – 1)union_par(id(i, j, 1), id(i, j + 1, 3)); // 多余 } } char c; for (int i = 0; i < n; ++i) { scanf("%c", &c); // 读取上一行的\n for (int j = 0; j < m; ++j) { scanf("%c", &c); if (c == ‘ ‘) { union_par(id(i, j, 0), id(i, j, 1)); union_par(id(i, j, 1), id(i, j, 2)); union_par(id(i, j, 2), id(i, j, 3)); //union_par(id(i, j, 3), id(i, j, 0)); // 多余 } else if (c == ‘/’) { union_par(id(i, j, 3), id(i, j, 0)); union_par(id(i, j, 1), id(i, j, 2)); } else if (c == ‘\\’) { union_par(id(i, j, 0), id(i, j, 1)); union_par(id(i, j, 2), id(i, j, 3)); } } } int ans = 0; for (int i = 0; i < total; ++i) { if (find_par(i) == i)++ans; } printf("%d\n", ans); return 0; } [/cpp] 本代码提交AC,用时4MS。]]>