# Problem A. Farthest Point(圆周上最远整点)
### Source
- [hihoCoder](http://hihocoder.com/contest/mstest2015sept2/problem/1)
### Problem
时间限制:5000ms
单点时限:1000ms
内存限制:256MB
### 描述
Given a circle on a two-dimentional plane.
Output the **integral** point in or on the boundary of the circle which hasthe largest distance from the center.
### 输入
One line with three floats which are all accurate to three decimal places,indicating the coordinates of the center x, y and the radius r.
For 80% of the data: |x|,|y|<=1000, 1<=r<=1000
For 100% of the data: |x|,|y|<=100000, 1<=r<=100000
### 输出
One line with two integers separated by one space, indicating the answer.
If there are multiple answers, print the one with the largest x-coordinate.
If there are still multiple answers, print the one with the largesty-coordinate.
#### 样例输入
~~~
1.000 1.000 5.000
~~~
#### 样例输出
~~~
6 1
~~~
### 题解1 - 圆周枚举
其实自己最开始做这道题时用的就是枚举,但是似乎忘记加圆心坐标了,一直 WA... 题目要求是返回最大的 x, 所以我们首先根据半径范围将 x 的整数解范围求出来。然后求出可能的 y, 由于题中给出的解有3位小数,如果要精确求解的话,可以将圆方程两边同乘1000,然后判断是否为整数。
### Java
~~~
import java.io.*;
import java.util.*;
import java.util.Queue;
class Point {
long x;
long y;
Point(long x, long y) {
this.x = x;
this.y = y;
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
double xd = in.nextDouble(), yd = in.nextDouble(), rd = in.nextDouble();
Point result = solve(xd, yd, rd);
System.out.println(result.x + " " + result.y);
}
private static Point solve(double x0, double y0, double r) {
// convert double to long(accurate)
long xl0 = (long)(x0 * 1000), yl0 = (long)(y0 * 1000), rl0 = (long)(r * 1000);
Point res = new Point(Long.MIN_VALUE, Long.MIN_VALUE);
int lower_x = (int)Math.ceil(x0 - r), upper_x = (int)Math.floor(x0 + r);
for (int i = upper_x; i >= lower_x; i--) {
// circle above
long y1l = yl0 + (long)(Math.sqrt(rl0*rl0 - (i*1000 - xl0)*(i*1000 - xl0)) + 0.5);
if ((i*1000 - xl0)*(i*1000 - xl0) + (y1l - yl0)*(y1l - yl0) == rl0*rl0) {
// ensure y1 is integer
if (y1l % 1000 == 0) {
res.x = i;
res.y = y1l / 1000;
return res;
}
}
// circle below
y1l = yl0 - (long)(Math.sqrt(rl0*rl0 - (i*1000 - xl0)*(i*1000 - xl0)) + 0.5);
if ((i*1000 - xl0)*(i*1000 - xl0) + (y1l - yl0)*(y1l - yl0) == rl0*rl0) {
// ensure y1 is integer
if (y1l % 1000 == 0) {
res.x = i;
res.y = y1l / 1000;
return res;
}
}
}
return res;
}
}
~~~
### 源码分析
自右向左枚举,先枚举圆的上半部分,再枚举圆的下半部分。注意1000的转换。
### 复杂度分析
最坏情况下 O(R)O(R)O(R).
### 题解2 - 整数分解
看似容易实则比较难的一道题,现场通过率非常低。我们仔细审下题,求圆周上的整点,有多个整点时输出最大的 x 和最大的 y. 容易想到的方案是枚举所有可能的 x 和 y, 然后代入等式测试是否相等,这个过不了大的 x 和 y. 如果用开方的方法必然有误差,我用这种方法不知道贡献了多少 WA, 泪流满面... 作为在线测试,**更为合理的方案应为先暴力搜索拿到百分之八十的分数。**
从 Microsoft 和 Google APAC 在线测试的风格来看是偏向于程序设计竞赛的,那么题目的考点自然就在竞赛范围之内,这道题看似是浮点型的数据,~~实际上考的却是整数中数论的基础。~~**注意题中的 accurate to three decimal places, 那么也就意味着我们对给定的数据同乘 10310^3103 后一定是整数!!**!这个关键的信息我在测试过程中也没注意到,直到第二天早上醒来后突然就想到了!兴奋地六点多就爬起来了。
首先肯定是要写出圆方程的,设圆心坐标为 (x0,y0)(x_0, y_0)(x0,y0), 半径为 rrr, 那么我们有:(x−x0)2+(y−y0)2=r2(x - x_0)^2 + (y - y_0)^2 = r^2(x−x0)2+(y−y0)2=r2
设 m=103(x−x0)m = 10^3(x - x_0)m=103(x−x0), n=103(y−y0)n = 10^3(y - y_0)n=103(y−y0), R=103rR = 10^3rR=103r, 那么我们有新的圆方程:m2+n2=R2m^2 + n^2 = R^2m2+n2=R2其中 `m, n, R` 均为整数。接下来我们看看给出的数据范围,x, y, r 均是 10610^6106 以内,那么圆方程两边同乘 10610^6106 (括号内的数即乘上 10310^3103)后数据在 101810^{18}1018 以内。我们来估算下整数的范围,210≈1032^{10} \approx 10^3210≈103, Java 中 int 型为4个字节,最大为 231−1≈2⋅1092^{31} - 1 \approx 2 \cdot 10^9231−1≈2⋅109, long 型为8个字节,最大为 263−1≈23⋅10182^{63} - 1 \approx 2^3 \cdot 10^{18}263−1≈23⋅1018, 估算下来应该选用 long 保存 m, n, R.
接下来就是数论部分的推导了,先来一个简单的推导,勾股数部分的推导不直观。首先从已知部分出发,已知的只有勾股数方程和 m, n 均是整数,那么接下来肯定是要利用整数的理论无疑了。我们首先对以上圆方程移项开方,考虑到圆的对称性,我们其实只需要考虑圆的八分之一即可。这里考虑`0 < m < r`部分,`m == 0`表示在点在轴上,最后单独加上。
m=R2−n2=(R+n)(R−n)m = \sqrt{R^2 - n^2} = \sqrt{(R + n)(R - n)}m=√R2−n2=√(R+n)(R−n)由于 m 一定是整数,故根号内一定为完全平方数,由于排除了轴上的点,那么`-R < n < R`, 设`G = gcd(R + n, R - n)`, p2=(R+n)/Gp^2 = (R + n) / Gp2=(R+n)/G, q2=(R−n)/Gq^2 = (R - n) / Gq2=(R−n)/G, 于是我们有`m = Gpq`, `p > q`, 由于`G` 是`R + n` 和`R - n` 的最大公约数,故`p` 和`q`一定互质,且有:p2+q2=2R/Gp^2 + q^2 = 2R / Gp2+q2=2R/G由于`p`,`q` 都大于等于1,那么我们能推断出`G` 一定是 `2R` 的约数!根据约数(素数)部分的基础理论,我们可以在 O(2R)O(\sqrt{2R})O(√2R) 时间内找出所有约数。然后对以上等式进行缩放得到`p` 的范围,枚举求解,判断`p^2` 和`q^2` 是否互质(最大公约数是否为1)。
### Java
~~~
import java.io.*;
import java.util.*;
class Point {
long x;
long y;
Point(long x, long y) {
this.x = x;
this.y = y;
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
double xd = in.nextDouble(), yd = in.nextDouble(), rd = in.nextDouble();
// convert double to long(accurate)
long x0 = (long)(xd * 1000), y0 = (long)(yd * 1000), r0 = (long)(rd * 1000);
Point result = solve(x0, y0, r0);
System.out.println(result.x + " " + result.y);
}
private static Point solve(long x0, long y0, long r) {
Point res = new Point(Long.MIN_VALUE, Long.MIN_VALUE);
long x_max = Long.MIN_VALUE, y_max = Long.MIN_VALUE;
// p^2 + q^2 = 2R/divisor, p > q >= 1
// 1 <= q^2 < R/G < p^2 < 2R/G ==> p >= 2
List<Long> divisors = getDivisor(r << 1);
for (long divisor : divisors) {
long lower = Math.max(2, (long)Math.sqrt(r * 1.0/ divisor));
// long upper = (long)Math.sqrt(2.0 * r / divisor);
for (long p = lower; p * p <= 2 * r / divisor; p++) {
long q = (long)Math.sqrt(2.0 * r / divisor - p * p);
// check if q is integer
if (p * p + q * q != 2 * r / divisor) continue;
// ensure p^2 and q^2 have no common divisor
if (gcd(p * p, q * q) == 1) {
long m = divisor * p * q;
long n = r - p * p * divisor;
List<Point> points = new ArrayList<Point>();
points.add(new Point(m + x0, n + y0));
points.add(new Point(m + x0, -1 * n + y0));
points.add(new Point(-1 * m + x0, n + y0));
points.add(new Point(-1 * m + x0, -1 * n + y0));
for (Point point : points) {
updateAns(point, res);
}
}
}
}
// axis point check
List<Point> axis = new ArrayList<Point>();
axis.add(new Point(x0 + r, y0));
axis.add(new Point(x0 - r, y0));
axis.add(new Point(x0, y0 + r));
axis.add(new Point(x0, y0 - r));
for (Point point : axis) {
updateAns(point, res);
}
// divide by 1000
res.x /= 1000;
res.y /= 1000;
return res;
}
public static void updateAns(Point p, Point res) {
// point(x, y) in integer
if ((p.x % 1000 == 0) && (p.y % 1000 == 0)) {
if (p.x > res.x) {
res.x = p.x;
res.y = p.y;
} else if (p.x == res.x && p.y > res.y) {
res.y = p.y;
}
}
}
// enumerate all the divisor for n
public static List<Long> getDivisor(long n) {
List<Long> result = new ArrayList<Long>();
for (long i = 1; i * i <= n; i++) {
if (n % i == 0) {
result.add(i);
// i * i <= n ==> i <= n / i
if (i != n / i) result.add(n / i);
}
}
Collections.sort(result);
return result;
}
public static long gcd(long a, long b) {
return (b == 0L) ? a : gcd(b, a % b);
}
}
~~~
### 源码分析
由于更新结果的操作非常频繁,单独写一个方法较好。
### 复杂度分析
求所有素数时间复杂度 O(n)O(\sqrt{n})O(√n), 判断是否互质时间复杂度 O(logn)O(\log n)O(logn). 枚举最大公约数时间复杂度约 (n)(\sqrt{n})(√n),总的时间复杂度估算应该比 O(n)O(n)O(n) 小一些,但是小的不明显。**所以说,这种方法费了老大劲,但是吃力不讨好!笔试中这种方法极不可取!**
### 题解3 - 勾股数
除了以上使用数论部分整数分解的方法外,还可以巧用勾股数的特性,这种方法需要熟知勾股数的特性。设正整数 m,n,rm, n, rm,n,r 满足:m2+n2=r2m^2 + n^2 = r^2m2+n2=r2我们对上式两边进行平方可得:(m2−n2)2+(2mn)2=(m2+n2)2=(r2)2(m^2 - n^2)^2 + (2mn)^2 = (m^2 + n^2)^2 = (r^2)^2(m2−n2)2+(2mn)2=(m2+n2)2=(r2)2令 a=m2−n2a = m^2 - n^2a=m2−n2, b=2mnb = 2mnb=2mn, c=m2+n2c = m^2 + n^2c=m2+n2. 容易得到:a2+b2=c2a^2 + b^2 = c^2a2+b2=c2注意到上述推导可逆,那么也就是说只要我们找到正整数满足`m > n`就能找到所有可能的勾股数。且根据素勾股数的特性,`m`, `n` 为一奇一偶,不妨设其为`2k-1`, `2k`. 代入`c`中可知`c`为`4K + 1`. 即`c % 4 = 1`. 根据 [Tree of primitive Pythagorean triples](https://en.wikipedia.org/wiki/Tree_of_primitive_Pythagorean_triples) 中提到的方法,我们只需找出小于给定的`r`的素勾股数即可,然后判断是否能整除`r`.
### Java
~~~
import java.io.*;
import java.util.*;
import java.util.Queue;
class Point {
long x;
long y;
Point(long x, long y) {
this.x = x;
this.y = y;
}
}
class Pythagorean {
long x;
long y;
long z;
Pythagorean(long x, long y, long z) {
this.x = x;
this.y = y;
this.z = z;
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
double xd = in.nextDouble(), yd = in.nextDouble(), rd = in.nextDouble();
// convert double to long(accurate)
long x0 = (long)(xd * 1000), y0 = (long)(yd * 1000), r0 = (long)(rd * 1000);
Point result = solve(x0, y0, r0);
System.out.println(result.x + " " + result.y);
}
private static Point solve(long x0, long y0, long r) {
Point res = new Point(Long.MIN_VALUE, Long.MIN_VALUE);
long x_max = Long.MIN_VALUE, y_max = Long.MIN_VALUE;
// init
Pythagorean pyth0 = new Pythagorean(3, 4, 5);
Queue<Pythagorean> q = new LinkedList<Pythagorean>();
q.offer(pyth0);
boolean update = true;
while (!q.isEmpty()) {
int qSize = q.size();
for (int i = 0; i < qSize; i++) {
Pythagorean pyth = q.poll();
if ((r % pyth.z) == 0) {
// System.out.println("x = " + pyth.x + ", y = " + pyth.y + ", r = " + pyth.z);
long k = r / pyth.z;
long kx = k * pyth.x;
long ky = k * pyth.y;
List<Point> points = new ArrayList<Point>();
points.add(new Point(x0 + kx, y0 + ky));
points.add(new Point(x0 - kx, y0 + ky));
points.add(new Point(x0 + kx, y0 - ky));
points.add(new Point(x0 - kx, y0 - ky));
if (kx != ky) {
points.add(new Point(y0 + ky, x0 + kx));
points.add(new Point(y0 - ky, x0 + kx));
points.add(new Point(y0 + ky, x0 - kx));
points.add(new Point(y0 - ky, x0 - kx));
}
for (Point point : points) {
updateAns(point, res);
}
}
// add next level Pythagorean
for (Pythagorean p : nextPyths(pyth)) {
if (p.z > r) continue;
q.offer(p);
}
}
}
// axis point check
List<Point> axis = new ArrayList<Point>();
axis.add(new Point(x0 + r, y0));
axis.add(new Point(x0 - r, y0));
axis.add(new Point(x0, y0 + r));
axis.add(new Point(x0, y0 - r));
for (Point point : axis) {
updateAns(point, res);
}
// divide by 1000
res.x /= 1000;
res.y /= 1000;
return res;
}
public static List<Pythagorean> nextPyths(Pythagorean pyth) {
List<Pythagorean> pyths = new ArrayList<Pythagorean>();
// method 1
Pythagorean pyth1 = new Pythagorean(0, 0, 0);
pyth1.x = pyth.x - 2 * pyth.y + 2 * pyth.z;
pyth1.y = 2 * pyth.x - 1 * pyth.y + 2 * pyth.z;
pyth1.z = 2 * pyth.x - 2 * pyth.y + 3 * pyth.z;
pyths.add(pyth1);
// method 2
Pythagorean pyth2 = new Pythagorean(0, 0, 0);
pyth2.x = pyth.x + 2 * pyth.y + 2 * pyth.z;
pyth2.y = 2 * pyth.x + 1 * pyth.y + 2 * pyth.z;
pyth2.z = 2 * pyth.x + 2 * pyth.y + 3 * pyth.z;
pyths.add(pyth2);
// method 3
Pythagorean pyth3 = new Pythagorean(0, 0, 0);
pyth3.x = -1 * pyth.x + 2 * pyth.y + 2 * pyth.z;
pyth3.y = -2 * pyth.x + pyth.y + 2 * pyth.z;
pyth3.z = -2 * pyth.x + 2 * pyth.y + 3 * pyth.z;
pyths.add(pyth3);
return pyths;
}
public static void updateAns(Point p, Point res) {
// point(x, y) in integer
if ((p.x % 1000 == 0) && (p.y % 1000 == 0)) {
if (p.x > res.x) {
res.x = p.x;
res.y = p.y;
} else if (p.x == res.x && p.y > res.y) {
res.y = p.y;
}
}
}
}
~~~
### 源码分析
根据链接中提到的数据结构,使用队列按层次遍历较好,但是空间消耗较大,所以在入队时一定要剪枝。
### 复杂度分析
时间复杂度最坏情况下需要遍历所有可能素勾股数。空间复杂度消耗也比较客观...
### Reference
- [BZOJ 1041 [HAOI2008] 圆上的整点 题解与分析 - 初学者 - 博客频道 - CSDN.NET](http://blog.csdn.net/csyzcyj/article/details/10044629)
- [[BZOJ1041 [HAOI2008]圆上的整点]数论、勾股数相关定理 | edward_mj](http://edward-mj.com/archives/166)
- [勾股数 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E5%8B%BE%E8%82%A1%E6%95%B0)
- [hihoCoder](http://hihocoder.com/discuss/question/2619)
- Preface
- Part I - Basics
- Basics Data Structure
- String
- Linked List
- Binary Tree
- Huffman Compression
- Queue
- Heap
- Stack
- Set
- Map
- Graph
- Basics Sorting
- Bubble Sort
- Selection Sort
- Insertion Sort
- Merge Sort
- Quick Sort
- Heap Sort
- Bucket Sort
- Counting Sort
- Radix Sort
- Basics Algorithm
- Divide and Conquer
- Binary Search
- Math
- Greatest Common Divisor
- Prime
- Knapsack
- Probability
- Shuffle
- Basics Misc
- Bit Manipulation
- Part II - Coding
- String
- strStr
- Two Strings Are Anagrams
- Compare Strings
- Anagrams
- Longest Common Substring
- Rotate String
- Reverse Words in a String
- Valid Palindrome
- Longest Palindromic Substring
- Space Replacement
- Wildcard Matching
- Length of Last Word
- Count and Say
- Integer Array
- Remove Element
- Zero Sum Subarray
- Subarray Sum K
- Subarray Sum Closest
- Recover Rotated Sorted Array
- Product of Array Exclude Itself
- Partition Array
- First Missing Positive
- 2 Sum
- 3 Sum
- 3 Sum Closest
- Remove Duplicates from Sorted Array
- Remove Duplicates from Sorted Array II
- Merge Sorted Array
- Merge Sorted Array II
- Median
- Partition Array by Odd and Even
- Kth Largest Element
- Binary Search
- Binary Search
- Search Insert Position
- Search for a Range
- First Bad Version
- Search a 2D Matrix
- Search a 2D Matrix II
- Find Peak Element
- Search in Rotated Sorted Array
- Search in Rotated Sorted Array II
- Find Minimum in Rotated Sorted Array
- Find Minimum in Rotated Sorted Array II
- Median of two Sorted Arrays
- Sqrt x
- Wood Cut
- Math and Bit Manipulation
- Single Number
- Single Number II
- Single Number III
- O1 Check Power of 2
- Convert Integer A to Integer B
- Factorial Trailing Zeroes
- Unique Binary Search Trees
- Update Bits
- Fast Power
- Hash Function
- Count 1 in Binary
- Fibonacci
- A plus B Problem
- Print Numbers by Recursion
- Majority Number
- Majority Number II
- Majority Number III
- Digit Counts
- Ugly Number
- Plus One
- Linked List
- Remove Duplicates from Sorted List
- Remove Duplicates from Sorted List II
- Remove Duplicates from Unsorted List
- Partition List
- Two Lists Sum
- Two Lists Sum Advanced
- Remove Nth Node From End of List
- Linked List Cycle
- Linked List Cycle II
- Reverse Linked List
- Reverse Linked List II
- Merge Two Sorted Lists
- Merge k Sorted Lists
- Reorder List
- Copy List with Random Pointer
- Sort List
- Insertion Sort List
- Check if a singly linked list is palindrome
- Delete Node in the Middle of Singly Linked List
- Rotate List
- Swap Nodes in Pairs
- Remove Linked List Elements
- Binary Tree
- Binary Tree Preorder Traversal
- Binary Tree Inorder Traversal
- Binary Tree Postorder Traversal
- Binary Tree Level Order Traversal
- Binary Tree Level Order Traversal II
- Maximum Depth of Binary Tree
- Balanced Binary Tree
- Binary Tree Maximum Path Sum
- Lowest Common Ancestor
- Invert Binary Tree
- Diameter of a Binary Tree
- Construct Binary Tree from Preorder and Inorder Traversal
- Construct Binary Tree from Inorder and Postorder Traversal
- Subtree
- Binary Tree Zigzag Level Order Traversal
- Binary Tree Serialization
- Binary Search Tree
- Insert Node in a Binary Search Tree
- Validate Binary Search Tree
- Search Range in Binary Search Tree
- Convert Sorted Array to Binary Search Tree
- Convert Sorted List to Binary Search Tree
- Binary Search Tree Iterator
- Exhaustive Search
- Subsets
- Unique Subsets
- Permutations
- Unique Permutations
- Next Permutation
- Previous Permuation
- Unique Binary Search Trees II
- Permutation Index
- Permutation Index II
- Permutation Sequence
- Palindrome Partitioning
- Combinations
- Combination Sum
- Combination Sum II
- Minimum Depth of Binary Tree
- Word Search
- Dynamic Programming
- Triangle
- Backpack
- Backpack II
- Minimum Path Sum
- Unique Paths
- Unique Paths II
- Climbing Stairs
- Jump Game
- Word Break
- Longest Increasing Subsequence
- Palindrome Partitioning II
- Longest Common Subsequence
- Edit Distance
- Jump Game II
- Best Time to Buy and Sell Stock
- Best Time to Buy and Sell Stock II
- Best Time to Buy and Sell Stock III
- Best Time to Buy and Sell Stock IV
- Distinct Subsequences
- Interleaving String
- Maximum Subarray
- Maximum Subarray II
- Longest Increasing Continuous subsequence
- Longest Increasing Continuous subsequence II
- Graph
- Find the Connected Component in the Undirected Graph
- Route Between Two Nodes in Graph
- Topological Sorting
- Word Ladder
- Bipartial Graph Part I
- Data Structure
- Implement Queue by Two Stacks
- Min Stack
- Sliding Window Maximum
- Longest Words
- Heapify
- Problem Misc
- Nuts and Bolts Problem
- String to Integer
- Insert Interval
- Merge Intervals
- Minimum Subarray
- Matrix Zigzag Traversal
- Valid Sudoku
- Add Binary
- Reverse Integer
- Gray Code
- Find the Missing Number
- Minimum Window Substring
- Continuous Subarray Sum
- Continuous Subarray Sum II
- Longest Consecutive Sequence
- Part III - Contest
- Google APAC
- APAC 2015 Round B
- Problem A. Password Attacker
- Microsoft
- Microsoft 2015 April
- Problem A. Magic Box
- Problem B. Professor Q's Software
- Problem C. Islands Travel
- Problem D. Recruitment
- Microsoft 2015 April 2
- Problem A. Lucky Substrings
- Problem B. Numeric Keypad
- Problem C. Spring Outing
- Microsoft 2015 September 2
- Problem A. Farthest Point
- Appendix I Interview and Resume
- Interview
- Resume