Problem:
Score: 500 points

Problem Statement

We have caught N sardines. The deliciousness and fragrantness of the i-th sardine is A_i and B_i, respectively.

We will choose one or more of these sardines and put them into a cooler. However, two sardines on bad terms cannot be chosen at the same time.

The i-th and j-th sardines (i \neq j) are on bad terms if and only if A_i \cdot A_j + B_i \cdot B_j = 0.

In how many ways can we choose the set of sardines to put into the cooler? Since the count can be enormous, print it modulo 1000000007.

Constraints

All values in input are integers.

1 \leq N \leq 2 \times 10^5

-10^{18} \leq A_i, B_i \leq 10^{18}

Input

Input is given from Standard Input in the following format:

N
A_1 B_1
:
A_N B_N

Output

Print the count modulo 1000000007.

Sample Input 1

3
1 2
-1 1
2 -1

Sample Output 1

5

There are five ways to choose the set of sardines, as follows:

The 1-st

The 1-st and 2-nd

The 2-nd

The 2-nd and 3-rd

The 3-rd

Sample Input 2

10
3 2
3 2
-1 1
2 -1
-3 -9
-8 12
7 7
8 1
8 2
8 4

Sample Output 2

479

Solution in C# (.NET Core 3.1.201) (judge verdict: Accepted)

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;

class Program
{
    const string Yes = "Yes";
    const string No = "No";
    const char White = '.';
    const char Black = '#';
    const long Mod = 1000000007;

    static void Main(string[] args) {
#if DEBUG
        MyDebugger.Test(process, "../TextFile1.txt");
#else
        object ret = process(new StreamReader(Console.OpenStandardInput()));
        Console.WriteLine(ret);
#endif
    }

    static object process(TextReader input) {
        var n = int.Parse(input.ReadLine());
        
        var tbl = new long[n][];
        for (int i = 0; i < tbl.Length; i++) {
            tbl[i] = ToIntArray(input.ReadLine(), 2);
        }

        var dicAB = new DefaultDectionary<RationalNumber, long>(0);
        long Azero = 0;
        long Bzero = 0;
        long ABzero = 0;

        foreach (var ab in tbl) {
            var a = ab[0];
            var b = ab[1];
            if (a != 0 && b != 0) {
                //dicAB[new RationalNumber(a, b)]++;
                var z = new RationalNumber(a, b);
                dicAB[z]++;
            }
            else if(a == 0 && b == 0) {
                ABzero++;
            }else if(a == 0) {
                Azero++;
            }else if(b == 0) {
                Bzero++;
            }
        }

        long sum = 1;

        HashSet<RationalNumber> counted = new HashSet<RationalNumber>();

        foreach(var x in dicAB) {
            //Debug.WriteLine(x);
            if(counted.Contains(x.Key)) continue;

            var z = x.Key.Inverse() * -1;
            counted.Add(z);

            var x1 = x.Value;
            var x2 = dicAB[z];

            long ss1 = ModPow(2, x1);
            long ss2 = ModPow(2, x2);
            long add = (ss1 + ss2 - 1) % Mod;
            sum *= add;
            sum %= Mod;
        }

        long zeroadd = (ModPow(2, Azero) + ModPow(2, Bzero) - 1) % Mod;
        sum *= zeroadd;
        sum %= Mod;

        sum += ABzero;
        sum %= Mod;

        sum -= 1; //All zero
        if(sum < 0) sum += Mod;
        return sum;
    }

    static long[] ToIntArray(string strs, int n) {
        var ret = new long[n];
        var spl = strs.Split(' ');
        for (int i = 0; i < n; i++)
            ret[i] = long.Parse(spl[i]);
        return ret;
    }

    static long ModPow(long x1, long x2) {
        long ans = 1;
        while (x2 > 0) {
            if (x2 % 2 == 1) {
                ans = (ans * x1) % Mod;
            }
            x1 = (x1 * x1) % Mod;
            x2 /= 2;
        }
        return ans % Mod;
    }
}

class DefaultDectionary<Tkey, TValue> : Dictionary<Tkey, TValue>
{
    TValue DefaultValue;
    public DefaultDectionary(TValue defaultvalue) {
        this.DefaultValue = defaultvalue;
    }
    public new TValue this[Tkey key] {
        get {
            if (this.ContainsKey(key)) return base[key];
            else return DefaultValue;
        }
        set {
            base[key] = value;
        }
    }
}

class RationalNumber : IEquatable<RationalNumber>, IEquatable<long>
{
    private bool sign = true;
    private long a = 0;
    private long b = 1;

    private static Regex Pattern
        = new Regex("\\s*(-?[0-9]+)\\s*(/\\s*([0-9]+)\\s*)?");

    //--------------------

    /// <summary>
    /// 分子の絶対値を取得します。
    /// </summary>
    public long Numerator {
        get { return a; }
    }

    /// <summary>
    /// 分母の絶対値を取得します。
    /// </summary>
    public long Denominator {
        get { return b; }
    }

    /// <summary>
    /// 小数値を取得します。
    /// </summary>
    public double DoubleValue {
        get {
            var v = (double)a / (double)b;
            return SignValue * v;
        }
    }
    /// <summary>
    /// 値が0であるかを取得します。
    /// </summary>
    public bool IsZero {
        get { return a == 0; }
    }

    /// <summary>
    /// 正の数の場合1、負の数の場合-1を取得します。
    /// </summary>
    public int SignValue {
        get { return this.sign ? 1 : -1; }
    }

    //--------------------

    /// <summary>
    /// 0を生成します。
    /// </summary>
    public RationalNumber() {
    }

    /// <summary>
    /// 分子と分母を指定して数を生成します。
    /// </summary>
    /// <param name="a">分子</param>
    /// <param name="b">分母</param>
    public RationalNumber(long a, long b = 1) {
        if (b == 0) {
            throw new DivideByZeroException("分母が0に指定されました。");
        }
        if (a == 0) {
            sign = true;
            this.a = 0;
            this.b = 1;
        } else {
            sign = IsSign(a, b);
            this.a = Math.Abs(a);
            this.b = Math.Abs(b);
            Reduction();
        }
    }

    //--------------------

    /// <summary>
    /// 分数形式で表記されたの文字列から数を生成します。
    /// </summary>
    /// <param name="s">分数または整数式の文字列</param>
    public static RationalNumber Parse(string s) {
        var mc = Pattern.Match(s);
        if (!mc.Success) {
            throw new FormatException("文字列が分数の形式ではありません。");
        }
        var a = long.Parse(mc.Groups[1].Value);
        var b = mc.Groups[3].Success ? long.Parse(mc.Groups[3].Value) : 1;
        return new RationalNumber(a, b);
    }

    /// <summary>
    /// 符号を検証します。
    /// </summary>
    private bool IsSign(long a, long b) {
        if (a >= 0 && b >= 0) return true;
        if (a < 0 && b < 0) return true;
        return false;
    }

    /// <summary>
    /// 約分します。
    /// </summary>
    private void Reduction() {
        var c = Gcd(a, b);
        a /= c;
        b /= c;
    }

    /// <summary>
    /// 逆数を取得します。
    /// </summary>
    /// <returns>この有理数の逆数を表すオブジェクト</returns>
    public RationalNumber Inverse() {
        return new RationalNumber(b * SignValue, a);
    }

    /// <summary>
    /// 約分された分数の文字列表現を取得します。
    /// </summary>
    public override string ToString() {
        return $"{(sign ? "" : "-")}{a}{(b > 1 ? $"/{b}" : "")}";
    }

    /// <summary>
    /// 数値の一致を評価します。
    /// </summary>
    public bool Equals(RationalNumber other) {
        return sign == other.sign
            && a == other.a && b == other.b;
    }

    /// <summary>
    /// 数値の一致を評価します。
    /// </summary>
    public bool Equals(long v) {
        if (b != 1) return false;
        var aa = SignValue * a;
        return aa == v;
    }

    public override int GetHashCode() {
        //return sign.GetHashCode()
        //    ^ a.GetHashCode() 
        //    ^ b.GetHashCode();
        return ToString().GetHashCode();
    }

    //--------------------

    //演算
    public static RationalNumber operator +(RationalNumber x1, RationalNumber x2) {
        var c = Lcm(x1.b, x2.b);
        var x1a = x1.a * (c / x1.b) * x1.SignValue;
        var x2a = x2.a * (c / x2.b) * x2.SignValue;
        return new RationalNumber(x1a + x2a, c);
    }
    public static RationalNumber operator -(RationalNumber x1, RationalNumber x2) {
        var c = Lcm(x1.b, x2.b);
        var x1a = x1.a * (c / x1.b) * x1.SignValue;
        var x2a = x2.a * (c / x2.b) * x2.SignValue;
        return new RationalNumber(x1a - x2a, c);
    }
    public static RationalNumber operator *(RationalNumber x1, RationalNumber x2) {
        var x1a = x1.a * x1.SignValue;
        var x2a = x2.a * x2.SignValue;
        return new RationalNumber(x1a * x2a, x1.b * x2.b);
    }
    public static RationalNumber operator /(RationalNumber x1, RationalNumber x2) {
        var x1a = x1.a * x1.SignValue;
        var x2a = x2.a * x2.SignValue;
        return new RationalNumber(x1a * x2.b, x2a * x1.b);
    }
    public static bool operator ==(RationalNumber x1, RationalNumber x2) {
        return x1.Equals(x2);
    }
    public static bool operator !=(RationalNumber x1, RationalNumber x2) {
        return !x1.Equals(x2);
    }
    public static bool operator >(RationalNumber x1, RationalNumber x2) {
        var c = Lcm(x1.b, x2.b);
        var x1a = x1.a * x1.SignValue * c;
        var x2a = x2.a * x2.SignValue * c;
        return x1a > x2a;
    }
    public static bool operator >=(RationalNumber x1, RationalNumber x2) {
        return x1 == x2 || x1 > x2;
    }
    public static bool operator <(RationalNumber x1, RationalNumber x2) {
        return !(x1 >= x2);
    }
    public static bool operator <=(RationalNumber x1, RationalNumber x2) {
        return !(x1 > x2);
    }

    //整数との演算
    public static RationalNumber operator +(RationalNumber x1, long i) {
        var x1a = x1.a * x1.SignValue;
        return new RationalNumber(x1a + i * x1.b, x1.b);
    }
    public static RationalNumber operator -(RationalNumber x1, long i) {
        var x1a = x1.a * x1.SignValue;
        return new RationalNumber(x1a - i * x1.b, x1.b);
    }
    public static RationalNumber operator *(RationalNumber x1, long i) {
        var x1a = x1.a * x1.SignValue;
        return new RationalNumber(x1a * i, x1.b);
    }
    public static RationalNumber operator /(RationalNumber x1, long i) {
        if (i == 0) throw new DivideByZeroException("0で除算を行いました。");
        var x1a = x1.a * x1.SignValue;
        return new RationalNumber(x1a, x1.b * i);
    }
    public static bool operator ==(RationalNumber x1, long i) {
        return x1.Equals(i);
    }
    public static bool operator !=(RationalNumber x1, long i) {
        return !x1.Equals(i);
    }
    public static bool operator >(RationalNumber x1, long i) {
        return x1.a * x1.SignValue > i * x1.b;
    }
    public static bool operator >=(RationalNumber x1, long i) {
        return x1 == i || x1 > i;
    }
    public static bool operator <(RationalNumber x1, long i) {
        return !(x1 >= i);
    }
    public static bool operator <=(RationalNumber x1, long i) {
        return !(x1 > i);
    }

    /// <summary>
    /// 最大公約数
    /// </summary>
    private static long Gcd(long m, long n) {
        var x1 = Math.Max(m, n);
        var x2 = Math.Min(m, n);

        while (true) {
            var mod = x1 % x2;
            if (mod == 0) return x2;
            x1 = x2;
            x2 = mod;
        }
    }

    /// <summary>
    /// 最小公倍数
    /// </summary>
    private static long Lcm(long m, long n) {
        var x1 = Math.Max(m, n);
        var x2 = Math.Min(m, n);

        var gcd = Gcd(x1, x2);
        return x1 / gcd * x2;
    }
}