Problem:
Score : 300 points

Problem Statement

There are N balls in a two-dimensional plane. The i-th ball is at coordinates (x_i, y_i).

We will collect all of these balls, by choosing two integers p and q such that p \neq 0 or q \neq 0 and then repeating the following operation:

Choose a ball remaining in the plane and collect it. Let (a, b) be the coordinates of this ball. If we collected a ball at coordinates (a - p, b - q) in the previous operation, the cost of this operation is 0. Otherwise, including when this is the first time to do this operation, the cost of this operation is 1.

Find the minimum total cost required to collect all the balls when we optimally choose p and q.

Constraints

1 \leq N \leq 50

|x_i|, |y_i| \leq 10^9

If i \neq j, x_i \neq x_j or y_i \neq y_j.

All values in input are integers.

Input

Input is given from Standard Input in the following format:

N
x_1 y_1
:
x_N y_N

Output

Print the minimum total cost required to collect all the balls.

Sample Input 1

2
1 1
2 2

Sample Output 1

1

If we choose p = 1, q = 1, we can collect all the balls at a cost of 1 by collecting them in the order (1, 1), (2, 2).

Sample Input 2

3
1 4
4 6
7 8

Sample Output 2

1

If we choose p = -3, q = -2, we can collect all the balls at a cost of 1 by collecting them in the order (7, 8), (4, 6), (1, 4).

Sample Input 3

4
1 1
1 2
2 1
2 2

Sample Output 3

2

Solution in Rust (1.15.1) (judge verdict: Accepted)

use std::cmp::*;
use std::collections::*;
use std::io::*;
use std::str::*;
use std::string::*;
use std::f64::consts::PI;
use std::collections::hash_set::Union;

#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Rev<T>(pub T);

impl<T: PartialOrd> PartialOrd for Rev<T> {
    fn partial_cmp(&self, other: &Rev<T>) -> Option<std::cmp::Ordering> {
        other.0.partial_cmp(&self.0)
    }
}

impl<T: Ord> Ord for Rev<T> {
    fn cmp(&self, other: &Rev<T>) -> std::cmp::Ordering {
        other.0.cmp(&self.0)
    }
}

macro_rules! read {
    (($($t:tt),*)) => {
        ( $(read!($t)),* )
    };
    ([[$t:tt; $len1:expr]; $len2:expr]) => {
        (0..$len2).map(|_| read!([$t; $len1])).collect::<Vec<_>>()
    };

    ([$t:tt; $len:expr]) => {
        (0..$len).map(|_| read!($t)).collect::<Vec<_>>()
    };

    (chars) => {
        read!(String).chars().collect::<Vec<char>>()
    };

    (usize1) => {
        read!(usize) - 1
    };

    ($t:ty) => {{
        let stdin = stdin();
        let stdin = stdin.lock();
        let token: String = stdin
            .bytes()
            .map(|c| c.unwrap() as char)
            .skip_while(|c| c.is_whitespace())
            .take_while(|c| !c.is_whitespace())
            .collect();

        token.parse::<$t>().unwrap()
    }};
}

macro_rules! input {
    (mut $name:ident: $t:tt, $($r:tt)*) => {
        let mut $name = read!($t);
        input!($($r)*);
    };

    (mut $name:ident: $t:tt) => {
        let mut $name = read!($t);
    };

    ($name:ident: $t:tt, $($r:tt)*) => {
        let $name = read!($t);
        input!($($r)*);
    };

    ($name:ident: $t:tt) => {
        let $name = read!($t);
    };
}

fn prime_factorization(n: i64) -> HashMap<i64, i64> {
    let mut n = n;
    let mut res = HashMap::new();
    let mut i = 2;
    while i * i <= n {
        while n % i == 0 {
            n /= i;
            let count = res.entry(i).or_insert(0);
            *count += 1;
        }
        i += 1;
    }

    if n > 1 {
        res.insert(n, 1);
    }

    return res;
}


struct Combination {
    MOD: i64,
    fac: Vec<i64>,
    fac_inv: Vec<i64>,
}

impl Combination {
    pub fn new(n: i64) -> Self {
        let MOD: i64 = 1_000_000_007;
        let mut fac: Vec<i64> = vec![0; n as usize + 1];
        let mut fac_inv: Vec<i64> = vec![0; n as usize + 1];

        let get_inverse = |mut n: i64| -> i64 {
            let (mut res, mut p) = (1, MOD - 2);

            while p != 0 {
                if p & 1 == 1 {
                    res = (res * n) % MOD;
                }
                n = (n * n) % MOD;
                p >>= 1;
            }

            return res;
        };

        fac[0] = 1;

        for i in 1..n + 1 {
            fac[i as usize] = (fac[i as usize - 1] * i) % MOD;
        }

        for i in 0..n + 1 {
            fac_inv[i as usize] = get_inverse(fac[i as usize]);
        }

        Combination {
            MOD: MOD,
            fac: fac,
            fac_inv: fac_inv,
        }
    }

    fn nCr(&self, n: i64, r: i64) -> i64 {
        if n < r {
            return 0;
        }

        let a: i64 = self.fac[n as usize];
        let b: i64 = self.fac_inv[(n - r) as usize];
        let c: i64 = self.fac_inv[r as usize];
        let bc: i64 = (b * c) % self.MOD;

        return (a * bc) % self.MOD;
    }

    fn nPr(&self, n: i64, r: i64) -> i64 {
        if n < r {
            return 0;
        }

        let a: i64 = self.fac[n as usize];
        let b: i64 = self.fac_inv[(n - r) as usize];

        return (a * b) % self.MOD;
    }

    fn nHr(&self, n: i64, r: i64) -> i64 {
        if n == 0 && r == 0 {
            return 1;
        }

        return self.nCr(n + r - 1, r);
    }
}

pub trait SliceExt<T>: Iterator + Sized {
    fn vec(self) -> Vec<Self::Item> {
        self.collect()
    }
}

#[derive(Copy, Clone, Eq, PartialEq)]
struct Edge {
    to: usize,
    cost: i64,
}

fn dijkstra(graph: &Vec<Vec<Edge>>, s: &usize) -> Vec<i64> {
    use std::collections::BinaryHeap;
    let mut dist = vec![1e18 as i64; graph.len()];
    let mut heap = BinaryHeap::new();
    dist[*s] = 0;
    heap.push(Rev((0, *s)));
    while let Some(Rev((cost, v))) = heap.pop() {
        if dist[v] < cost {
            continue;
        }

        for e in &graph[v] {
            if dist[e.to] <= dist[v] + e.cost {
                continue;
            }
            dist[e.to] = dist[v] + e.cost;
            heap.push(Rev((dist[e.to], e.to)));
        }
    }
    return dist;
}

struct LCA {
    par: Vec<Vec<Option<usize>>>,
    dist: Vec<i64>,
}

impl LCA {
    pub fn new(graph: &Vec<Vec<usize>>, root: &usize) -> LCA {
        let V = graph.len();
        let mut K = 1;

        while (1 << K) < V {
            K += 1;
        }

        let mut par = vec![vec![None; V]; K];
        let mut dist = vec![-1; V];
        let graph = graph.to_vec();

        fn dfs(
            v: usize,
            p: Option<usize>,
            d: i64,
            graph: &Vec<Vec<usize>>,
            par: &mut Vec<Vec<Option<usize>>>,
            dist: &mut Vec<i64>,
        ) {
            par[0][v] = p;
            dist[v] = d;
            for &to in &graph[v] {
                match p {
                    Some(p) => {
                        if to != p {
                            dfs(to, Some(v), d + 1, graph, par, dist)
                        }
                    }
                    None => dfs(to, Some(v), d + 1, graph, par, dist),
                }
            }
        }

        dfs(*root, None, 0, &graph, &mut par, &mut dist);

        for k in 0..K - 1 {
            for v in 0..V {
                match par[k][v] {
                    Some(x) => par[k + 1][v] = par[k][x],
                    None => (),
                }
            }
        }

        LCA {
            par: par,
            dist: dist,
        }
    }

    pub fn query(&self, u: usize, v: usize) -> usize {
        let mut u = u;
        let mut v = v;
        if self.dist[u] < self.dist[v] {
            return self.query(v, u);
        }

        let K = self.par.len();
        for k in 0..K {
            if ((self.dist[u] - self.dist[v]) >> k & 1) == 1 {
                u = self.par[k][u].unwrap();
            }
        }

        if u == v {
            return u;
        }

        for k in (0..K - 1).rev() {
            if self.par[k][u] != self.par[k][v] {
                u = self.par[k][u].unwrap();
                v = self.par[k][v].unwrap();
            }
        }

        return self.par[0][u].unwrap();
    }
}

struct Doubling {
    N: usize,
    LOG: usize,
    next: Vec<Vec<Option<usize>>>,
}

impl Doubling {
    pub fn new(vec: &Vec<usize>, lim: &usize) -> Doubling {
        let N = vec.len();
        let lim = *lim;
        let mut LOG = 1;
        while (1 << LOG) < 2 * lim {
            LOG += 1;
        }

        let mut next = vec![vec![None; N]; LOG];

        for i in 0..N {
            next[0][i] = Some(vec[i]);
        }

        for k in 0..LOG - 1 {
            for i in 0..N {
                match next[k][i] {
                    Some(x) => next[k + 1][i] = next[k][x],
                    None => (),
                }
            }
        }

        Doubling {
            N: N,
            LOG: LOG,
            next: next,
        }
    }

    pub fn query(&self, i: usize, n: usize) -> usize {
        let mut i = i;
        for k in (0..self.LOG).rev() {
            if (n >> k) & 1 == 1 {
                i = self.next[k][i].unwrap();
            }
        }
        return i;
    }
}

macro_rules! min {
    ($a:expr $(,)*) => {{
        $a
    }};
    ($a:expr, $b:expr $(,)*) => {{
        std::cmp::min($a, $b)
    }};
    ($a:expr, $($rest:expr),+ $(,)*) => {{
        std::cmp::min($a, min!($($rest),+))
    }};
}

macro_rules! chmin {
    ($base:expr, $($cmps:expr),+ $(,)*) => {{
        let cmp_min = min!($($cmps),+);
        if $base > cmp_min {
            $base = cmp_min;
            true
        } else {
            false
        }
    }};
}

macro_rules! max {
    ($a:expr $(,)*) => {{
        $a
    }};
    ($a:expr, $b:expr $(,)*) => {{
        std::cmp::max($a, $b)
    }};
    ($a:expr, $($rest:expr),+ $(,)*) => {{
        std::cmp::max($a, max!($($rest),+))
    }};
}

macro_rules! chmax {
    ($base:expr, $($cmps:expr),+ $(,)*) => {{
        let cmp_max = max!($($cmps),+);
        if $base < cmp_max {
            $base = cmp_max;
            true
        } else {
            false
        }
    }};
}

struct UnionFind {
    par: Vec<i32>,
}

impl UnionFind {
    pub fn new(n: usize) -> UnionFind {
        UnionFind {
            par: vec![-1; n],
        }
    }

    pub fn root(&mut self, x: usize) -> usize {
        if self.par[x] < 0 {
            return x;
        } else {
            let a = self.par[x] as usize;
            self.par[x] = self.root(a) as i32;
            return self.par[x] as usize;
        }
    }

    pub fn is_same(&mut self, x: usize, y: usize) -> bool {
        self.root(x) == self.root(y)
    }

    pub fn merge(&mut self, x: usize, y: usize) -> bool {
        let mut x = self.root(x);
        let mut y = self.root(y);
        if x == y {
            return false;
        } else {
            if self.par[x] > self.par[y] {
                std::mem::swap(&mut x, &mut y);
            }
            self.par[x] += self.par[y];
            self.par[y] = x as i32;
            return true;
        }
    }

    pub fn size(&mut self, x: usize) -> i64 {
        let a = self.root(x);
        return -self.par[a] as i64;
    }
}

trait Semigroup {
    fn op(&self, other: &Self) -> Self;
}

trait Monoid: Semigroup {
    fn identity() -> Self;
}

fn main() {
    input!(n: usize, mut v: [(i64, i64); n]);
    let mut ans = n;
    let x: Vec<i64> = (0..n).map(|i| v[i].0).collect();
    let y: Vec<i64> = (0..n).map(|i| v[i].1).collect();
    if n == 1 {
        println!("1");
        return;
    }

    let mut ans = n;
    for i in 0..n {
        for j in i + 1..n {
            let p = x[i] - x[j];
            let q = y[i] - y[j];

            let mut uf = UnionFind::new(n);
            for k in 0..n {
                for l in 0..n {
                    if x[k] + p == x[l] && y[k] + q == y[l] {
                        uf.merge(k, l);
                    }
                }
            }
            let mut sub = 0;
            for a in 0..n {
                if uf.root(a) == a {
                    sub += 1;
                }
            }
            chmin!(ans, sub);
        }
    }

    println!("{}", ans);
}