Ghosts live in harmony and peace, they travel the space without any purpose other than scare whoever stands in their way.

There are $n$ ghosts in the universe, they move in the $OXY$ plane, each one of them has its own velocity that does not change in time: $\overrightarrow{V} = V_{x}\overrightarrow{i} + V_{y}\overrightarrow{j}$ where $V_{x}$ is its speed on the $x$-axis and $V_{y}$ is on the $y$-axis.

A ghost $i$ has experience value $EX_i$, which represent how many ghosts tried to scare him in his past. Two ghosts scare each other if they were in the same cartesian point at a moment of time.

As the ghosts move with constant speed, after some moment of time there will be no further scaring (what a relief!) and the experience of ghost kind $GX = \sum_{i=1}^{n} EX_i$ will never increase.

Tameem is a red giant, he took a picture of the cartesian plane at a certain moment of time $T$, and magically all the ghosts were aligned on a line of the form $y = a \cdot x + b$. You have to compute what will be the experience index of the ghost kind $GX$ in the indefinite future, this is your task for today.

Note that when Tameem took the picture, $GX$ may already be greater than $0$, because many ghosts may have scared one another at any moment between $[-\infty, T]$.


-----Input-----

The first line contains three integers $n$, $a$ and $b$ ($1 \leq n \leq 200000$, $1 \leq |a| \leq 10^9$, $0 \le |b| \le 10^9$) — the number of ghosts in the universe and the parameters of the straight line.

Each of the next $n$ lines contains three integers $x_i$, $V_{xi}$, $V_{yi}$ ($-10^9 \leq x_i \leq 10^9$, $-10^9 \leq V_{x i}, V_{y i} \leq 10^9$), where $x_i$ is the current $x$-coordinate of the $i$-th ghost (and $y_i = a \cdot x_i + b$).

It is guaranteed that no two ghosts share the same initial position, in other words, it is guaranteed that for all $(i,j)$ $x_i \neq x_j$ for $i \ne j$.


-----Output-----

Output one line: experience index of the ghost kind $GX$ in the indefinite future.


-----Examples-----
Input
4 1 1
1 -1 -1
2 1 1
3 1 1
4 -1 -1

Output
8

Input
3 1 0
-1 1 0
0 0 -1
1 -1 -2

Output
6

Input
3 1 0
0 0 0
1 0 0
2 0 0

Output
0



-----Note-----

There are four collisions $(1,2,T-0.5)$, $(1,3,T-1)$, $(2,4,T+1)$, $(3,4,T+0.5)$, where $(u,v,t)$ means a collision happened between ghosts $u$ and $v$ at moment $t$. At each collision, each ghost gained one experience point, this means that $GX = 4 \cdot 2 = 8$.

In the second test, all points will collide when $t = T + 1$.  [Image] 

The red arrow represents the 1-st ghost velocity, orange represents the 2-nd ghost velocity, and blue represents the 3-rd ghost velocity.

Source: codeforces | Difficulty: HARD
Tags: Geometry

Mathematics


Solutions:
from collections import Counter
from sys import stdin
(n, a, b) = map(int, stdin.readline().split())
s = Counter()
ans = 0
for i in range(n):
	(x, vx, vy) = map(int, stdin.readline().split())
	tmp = vy - a * vx
	ans += s[tmp] - s[vx, vy]
	s[tmp] += 1
	s[vx, vy] += 1
print(ans * 2)


def main():
	(n, a, b) = [int(part) for part in input().split()]
	ghosts = []
	for i in range(n):
		ghosts.append([int(part) for part in input().split()])
	cnt = dict()
	vcnt = dict()
	for (x, vx, vy) in ghosts:
		if (vx, vy) in vcnt:
			vcnt[vx, vy] += 1
		else:
			vcnt[vx, vy] = 1
		tmp = vy - a * vx
		if tmp in cnt:
			cnt[tmp] += 1
		else:
			cnt[tmp] = 1
	ans = 0
	for (x, vx, vy) in ghosts:
		tmp = vy - a * vx
		ans += cnt[tmp] - vcnt[vx, vy]
	print(ans)

def __starting_point():
	main()
__starting_point()


(n, a, b) = [int(x) for x in input().split()]
mp = {}
spmp = {}
for i in range(n):
	(x, vx, vy) = [int(x) for x in input().split()]
	vv = a * vx - vy
	spr = str([vx, vy])
	if vv in mp:
		mp[vv] += 1
	else:
		mp[vv] = 1
	if spr in spmp:
		spmp[spr] += 1
	else:
		spmp[spr] = 1
ans = 0
for v in mp.values():
	ans += v * (v - 1)
for v in spmp.values():
	ans -= v * (v - 1)
print(ans)


from sys import stdin
rd = lambda : list(map(int, input().split()))
g = lambda x, i: x.get(i, 0)
(n, a, b) = rd()
(c, d) = ({}, {})
r = 0
for z in range(n):
	(_, x, y) = rd()
	(i, j) = (a * x - y, (x, y))
	r += g(c, i) - g(d, j)
	c[i] = g(c, i) + 1
	d[j] = g(d, j) + 1
print(2 * r)


from collections import *
f = lambda : map(int, input().split())
(n, a, b) = f()
s = Counter()
d = 0
for i in range(n):
	(x, u, v) = f()
	t = v - a * u
	d += s[t] - s[u, v]
	s[t] += 2
	s[u, v] += 2
print(d)


import sys
input = sys.stdin.readline
(N, A, B) = map(int, input().split())
POS = {}
for _ in range(N):
	(x, vx, vy) = map(int, input().split())
	v = (vx, vy)
	score = vy - A * vx
	if score not in POS:
		POS[score] = {}
	if v not in POS[score]:
		POS[score][v] = 0
	POS[score][v] += 1
COL = 0
for x in POS:
	size = sum([POS[x][v] for v in POS[x]])
	COL += size * (size - 1) // 2 - sum([max(0, POS[x][v] * (POS[x][v] - 1) // 2) for v in POS[x]])
COL *= 2
print(COL)


import sys
import io, os
input = io.BytesIO(os.read(0, os.fstat(0).st_size)).readline
from collections import defaultdict
(n, a, b) = map(int, input().split())
XV = []
for i in range(n):
	(x, vx, vy) = map(int, input().split())
	XV.append((x, vx, vy))
if a != 0:
	ans = 0
	d = defaultdict(lambda : 0)
	dvx = defaultdict(lambda : 0)
	dvy = defaultdict(lambda : 0)
	dvxy = defaultdict(lambda : 0)
	for (x, vx, vy) in XV:
		k = -a * vx + vy
		ans += max(0, d[k] - (dvx[k, vx] + dvy[k, vy] - dvxy[k, vx, vy]))
		d[k] += 1
		dvx[k, vx] += 1
		dvy[k, vy] += 1
		dvxy[k, vx, vy] += 1
	print(ans * 2)
else:
	ans = 0
	d = defaultdict(lambda : defaultdict(lambda : 0))
	ds = defaultdict(lambda : 0)
	for (x, vx, vy) in XV:
		ans += max(0, ds[vy] - d[vy][vx])
		d[vy][vx] += 1
		ds[vy] += 1
	print(ans * 2)


from collections import Counter
[n, a, b] = list(map(int, input().strip().split()))
bis = [tuple(map(int, input().strip().split())) for _ in range(n)]
dis = [(a * Vx - Vy, Vx) for (x, Vx, Vy) in bis]
dis.sort()

def count(d):
	return sum((v * k * (k - 1) for (k, v) in d))

def filt(it):
	return list(Counter(list(Counter(it).values())).items())
c_plus = count(filt((x for (x, y) in dis)))
c_minus = count(filt(dis))
print(c_plus - c_minus)


import atexit
import io
import sys
_INPUT_LINES = sys.stdin.read().splitlines()
input = iter(_INPUT_LINES).__next__
_OUTPUT_BUFFER = io.StringIO()
sys.stdout = _OUTPUT_BUFFER

@atexit.register
def write():
	sys.__stdout__.write(_OUTPUT_BUFFER.getvalue())

def main():
	(n, a, b) = [int(x) for x in input().split()]
	dc = {}
	for i in range(n):
		(x, vx, vy) = [int(x) for x in input().split()]
		nx = x + vx
		ny = a * x + b + vy
		dd = a * nx - ny + b
		if dd not in dc:
			dc[dd] = {}
		if (vx, vy) not in dc[dd]:
			dc[dd][vx, vy] = 0
		dc[dd][vx, vy] += 1
	tot = 0
	for (v, k) in list(dc.items()):
		tt = 0
		pp = 0
		for (_, cc) in list(k.items()):
			tt -= cc * (cc + 1) // 2
			pp += cc
		tt += pp * (pp + 1) // 2
		tot += tt * 2
	print(tot)

def __starting_point():
	main()
__starting_point()


from collections import Counter
(n, a, b) = [int(_) for _ in input().split()]
(s, s1) = (Counter(), Counter())
ans = 0
for i in range(n):
	(x, vx, vy) = [int(_) for _ in input().split()]
	tmp = vy - a * vx
	ans += s[tmp] - s1[vx, vy]
	s[tmp] += 1
	s1[vx, vy] += 1
print(ans * 2)


(n, a, b) = map(int, input().split())
ghosts = [(vx, vy) for (x, vx, vy) in (map(int, input().split()) for i in range(n))]
speeds = {}
for (vx, vy) in ghosts:
	vl = a * vx - vy
	k = vx + a * vy
	ss = speeds.setdefault(vl, {})
	ss[k] = ss.get(k, 0) + 1
result = 0
for (vl, ss) in speeds.items():
	group_size = sum(ss.values())
	for sss in ss.values():
		result += sss * (group_size - sss)
print(result)


import sys
import math
import bisect
import itertools
from collections import Counter
d = {}
s = {}
(n, a, b) = list(map(int, sys.stdin.readline().strip().split(' ')))
for n0 in range(n):
	(x, vxi, vyi) = list(map(int, sys.stdin.readline().strip().split(' ')))
	curr = a * vxi - vyi
	if curr in d:
		if (vxi, vyi) in d[curr]:
			d[curr][vxi, vyi] += 1
		else:
			d[curr][vxi, vyi] = 1
		s[curr] += 1
	else:
		d[curr] = {(vxi, vyi): 1}
		s[curr] = 1
ans = 0
for (k, v) in list(d.items()):
	for (vxy, q) in list(v.items()):
		ans += (s[k] - q) * q
print(ans)


(n, A, C) = list(map(int, input().split()))

def Ro(x, y):
	return A * x - y + C
huh = []
for i in range(n):
	(z, x, y) = list(map(int, input().split()))
	huh.append((Ro(x + z, z * A + y), x))
huh = sorted(huh)
anss = 0
c1 = 0
c2 = 0
prev = (-9999999999999, -999999999999999)
g = []
huh.append((-9999999999999, -999999999999999))
for huhh in huh:
	if huhh[0] != prev[0]:
		g.append(c1)
		for j in g:
			anss += (c2 - j) * j
		g = []
		c1 = 1
		c2 = 1
		prev = (huhh[0], huhh[1])
		continue
	c2 += 1
	if huhh[1] != prev[1]:
		g.append(c1)
		c1 = 0
		prev = (huhh[0], huhh[1])
	c1 += 1
print(anss)


[n, a, b] = list(map(int, input().strip().split()))
bis = [tuple(map(int, input().strip().split())) for _ in range(n)]
dis = [(a * Vx - Vy, Vx) for (x, Vx, Vy) in bis]
dis.sort()
res = 0
dprev = dis[0][0] - 1
vprev = 0
dcount = 0
vcount = 0
for (di, vi) in dis:
	if di != dprev:
		res += dcount * (dcount - 1)
		res -= vcount * (vcount - 1)
		dcount = 1
		vcount = 1
		dprev = di
		vprev = vi
	else:
		dcount += 1
		if vi != vprev:
			res -= vcount * (vcount - 1)
			vcount = 1
			vprev = vi
		else:
			vcount += 1
res += dcount * (dcount - 1)
res -= vcount * (vcount - 1)
print(res)


from collections import Counter
(n, a, b) = map(int, input().split())
s = Counter()
l = 0
for i in range(n):
	(x, vx, vy) = map(int, input().split())
	tmp = vy - a * vx
	l += s[tmp] - s[vx, vy]
	s[tmp] += 1
	s[vx, vy] += 1
print(l * 2)


from collections import defaultdict
(n, a, b) = map(int, input().split())
lit = [[0 for i in range(3)] for j in range(n)]
ans = 0
deft = defaultdict(int)
sim = defaultdict(int)
for i in range(n):
	(x, vx, vy) = map(int, input().split())
	tmp = str(vx) + ' ' + str(vy)
	ans += deft[a * vx - vy] - sim[tmp]
	deft[a * vx - vy] += 1
	sim[tmp] += 1
print(ans * 2)


from sys import stdin
from collections import Counter

def f(line):
	t = line.split()
	return tuple((int(x) for x in t[1:]))
(n, a, b) = [int(x) for x in stdin.readline().split()]
points = [f(line) for line in stdin.readlines()[:n]]
groups = Counter(points)
total = {}
for ((x, y), cnt) in groups.items():
	if y - x * a not in total:
		total[y - x * a] = 0
	total[y - x * a] += cnt
ans = 0
for ((x, y), cnt) in groups.items():
	if y - x * a not in total:
		continue
	ans += cnt * (total[y - x * a] - cnt)
print(ans)


from collections import Counter
import sys
(n, a, b) = list(map(int, sys.stdin.readline().split()))
c = Counter()
d = Counter()
ans = 0
for i in range(n):
	(x, vx, vy) = list(map(int, sys.stdin.readline().split()))
	tmp = vy - a * vx
	ans += c[tmp] - c[vx, vy] - d[tmp, x] + d[tmp, x, vx, vy] + d[x]
	c[tmp] += 1
	c[vx, vy] += 1
	d[tmp, x] += 1
	d[x] += 1
	d[tmp, x, vx, vy] += 1
sys.stdout.write(str(ans * 2))


import atexit
import io
import sys
_INPUT_LINES = sys.stdin.read().splitlines()
input = iter(_INPUT_LINES).__next__

def main():
	(n, a, b) = [int(x) for x in input().split()]
	dc = {}
	for i in range(n):
		(x, vx, vy) = [int(x) for x in input().split()]
		nx = x + vx
		ny = a * x + b + vy
		dd = a * nx - ny + b
		if dd not in dc:
			dc[dd] = {}
		if (vx, vy) not in dc[dd]:
			dc[dd][vx, vy] = 0
		dc[dd][vx, vy] += 1
	tot = 0
	for (v, k) in dc.items():
		tt = 0
		pp = 0
		for (_, cc) in k.items():
			tt -= cc * (cc + 1) // 2
			pp += cc
		tt += pp * (pp + 1) // 2
		tot += tt * 2
	print(tot)
main()


(n, a, b) = (int(x) for x in input().split())
marks = 0
k = {}
v = {}
for i in range(0, n):
	(x, v1, v2) = [int(x) for x in input().split()]
	ki = v2 - a * v1
	vi = (v1, v2)
	if ki in k:
		k[ki] += 1
	else:
		k[ki] = 1
	if vi in v:
		v[vi] += 1
	else:
		v[vi] = 1
	marks += k[ki] - v[vi]
print(marks * 2)


import math
import random
import heapq, bisect
import sys
from collections import deque, defaultdict
from fractions import Fraction
import sys
from collections import defaultdict
mod = 10 ** 9 + 7
mod1 = 998244353
import os
import sys
from io import BytesIO, IOBase
BUFSIZE = 8192

class FastIO(IOBase):
	newlines = 0

	def __init__(self, file):
		self._fd = file.fileno()
		self.buffer = BytesIO()
		self.writable = 'x' in file.mode or 'r' not in file.mode
		self.write = self.buffer.write if self.writable else None

	def read(self):
		while True:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			if not b:
				break
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines = 0
		return self.buffer.read()

	def readline(self):
		while self.newlines == 0:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			self.newlines = b.count(b'\n') + (not b)
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines -= 1
		return self.buffer.readline()

	def flush(self):
		if self.writable:
			os.write(self._fd, self.buffer.getvalue())
			(self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

	def __init__(self, file):
		self.buffer = FastIO(file)
		self.flush = self.buffer.flush
		self.writable = self.buffer.writable
		self.write = lambda s: self.buffer.write(s.encode('ascii'))
		self.read = lambda : self.buffer.read().decode('ascii')
		self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')

class TreeNode:

	def __init__(self, k, v):
		self.key = k
		self.value = v
		self.left = None
		self.right = None
		self.parent = None
		self.height = 1
		self.num_left = 1
		self.num_total = 1

class AvlTree:

	def __init__(self):
		self._tree = None

	def add(self, k, v):
		if not self._tree:
			self._tree = TreeNode(k, v)
			return
		node = self._add(k, v)
		if node:
			self._rebalance(node)

	def _add(self, k, v):
		node = self._tree
		while node:
			if k < node.key:
				if node.left:
					node = node.left
				else:
					node.left = TreeNode(k, v)
					node.left.parent = node
					return node.left
			elif node.key < k:
				if node.right:
					node = node.right
				else:
					node.right = TreeNode(k, v)
					node.right.parent = node
					return node.right
			else:
				node.value = v
				return

	@staticmethod
	def get_height(x):
		return x.height if x else 0

	@staticmethod
	def get_num_total(x):
		return x.num_total if x else 0

	def _rebalance(self, node):
		n = node
		while n:
			lh = self.get_height(n.left)
			rh = self.get_height(n.right)
			n.height = max(lh, rh) + 1
			balance_factor = lh - rh
			n.num_total = 1 + self.get_num_total(n.left) + self.get_num_total(n.right)
			n.num_left = 1 + self.get_num_total(n.left)
			if balance_factor > 1:
				if self.get_height(n.left.left) < self.get_height(n.left.right):
					self._rotate_left(n.left)
				self._rotate_right(n)
			elif balance_factor < -1:
				if self.get_height(n.right.right) < self.get_height(n.right.left):
					self._rotate_right(n.right)
				self._rotate_left(n)
			else:
				n = n.parent

	def _remove_one(self, node):
		replacement = node.left or node.right
		if node.parent:
			if AvlTree._is_left(node):
				node.parent.left = replacement
			else:
				node.parent.right = replacement
			replacement.parent = node.parent
			node.parent = None
		else:
			self._tree = replacement
			replacement.parent = None
		node.left = None
		node.right = None
		node.parent = None
		self._rebalance(replacement)

	def _remove_leaf(self, node):
		if node.parent:
			if AvlTree._is_left(node):
				node.parent.left = None
			else:
				node.parent.right = None
			self._rebalance(node.parent)
		else:
			self._tree = None
		node.parent = None
		node.left = None
		node.right = None

	def remove(self, k):
		node = self._get_node(k)
		if not node:
			return
		if AvlTree._is_leaf(node):
			self._remove_leaf(node)
			return
		if node.left and node.right:
			nxt = AvlTree._get_next(node)
			node.key = nxt.key
			node.value = nxt.value
			if self._is_leaf(nxt):
				self._remove_leaf(nxt)
			else:
				self._remove_one(nxt)
			self._rebalance(node)
		else:
			self._remove_one(node)

	def get(self, k):
		node = self._get_node(k)
		return node.value if node else -1

	def _get_node(self, k):
		if not self._tree:
			return None
		node = self._tree
		while node:
			if k < node.key:
				node = node.left
			elif node.key < k:
				node = node.right
			else:
				return node
		return None

	def get_at(self, pos):
		x = pos + 1
		node = self._tree
		while node:
			if x < node.num_left:
				node = node.left
			elif node.num_left < x:
				x -= node.num_left
				node = node.right
			else:
				return (node.key, node.value)
		raise IndexError('Out of ranges')

	@staticmethod
	def _is_left(node):
		return node.parent.left and node.parent.left == node

	@staticmethod
	def _is_leaf(node):
		return node.left is None and node.right is None

	def _rotate_right(self, node):
		if not node.parent:
			self._tree = node.left
			node.left.parent = None
		elif AvlTree._is_left(node):
			node.parent.left = node.left
			node.left.parent = node.parent
		else:
			node.parent.right = node.left
			node.left.parent = node.parent
		bk = node.left.right
		node.left.right = node
		node.parent = node.left
		node.left = bk
		if bk:
			bk.parent = node
		node.height = max(self.get_height(node.left), self.get_height(node.right)) + 1
		node.num_total = 1 + self.get_num_total(node.left) + self.get_num_total(node.right)
		node.num_left = 1 + self.get_num_total(node.left)

	def _rotate_left(self, node):
		if not node.parent:
			self._tree = node.right
			node.right.parent = None
		elif AvlTree._is_left(node):
			node.parent.left = node.right
			node.right.parent = node.parent
		else:
			node.parent.right = node.right
			node.right.parent = node.parent
		bk = node.right.left
		node.right.left = node
		node.parent = node.right
		node.right = bk
		if bk:
			bk.parent = node
		node.height = max(self.get_height(node.left), self.get_height(node.right)) + 1
		node.num_total = 1 + self.get_num_total(node.left) + self.get_num_total(node.right)
		node.num_left = 1 + self.get_num_total(node.left)

	@staticmethod
	def _get_next(node):
		if not node.right:
			return node.parent
		n = node.right
		while n.left:
			n = n.left
		return n

class SegmentTree1:

	def __init__(self, data, default=300006, func=lambda a, b: min(a, b)):
		self._default = default
		self._func = func
		self._len = len(data)
		self._size = _size = 1 << (self._len - 1).bit_length()
		self.data = [default] * (2 * _size)
		self.data[_size:_size + self._len] = data
		for i in reversed(range(_size)):
			self.data[i] = func(self.data[i + i], self.data[i + i + 1])

	def __delitem__(self, idx):
		self[idx] = self._default

	def __getitem__(self, idx):
		return self.data[idx + self._size]

	def __setitem__(self, idx, value):
		idx += self._size
		self.data[idx] = value
		idx >>= 1
		while idx:
			self.data[idx] = self._func(self.data[2 * idx], self.data[2 * idx + 1])
			idx >>= 1

	def __len__(self):
		return self._len

	def query(self, start, stop):
		if start == stop:
			return self.__getitem__(start)
		stop += 1
		start += self._size
		stop += self._size
		res = self._default
		while start < stop:
			if start & 1:
				res = self._func(res, self.data[start])
				start += 1
			if stop & 1:
				stop -= 1
				res = self._func(res, self.data[stop])
			start >>= 1
			stop >>= 1
		return res

	def __repr__(self):
		return 'SegmentTree({0})'.format(self.data)

class SegmentTree:

	def __init__(self, data, default=0, func=lambda a, b: a + b):
		self._default = default
		self._func = func
		self._len = len(data)
		self._size = _size = 1 << (self._len - 1).bit_length()
		self.data = [default] * (2 * _size)
		self.data[_size:_size + self._len] = data
		for i in reversed(range(_size)):
			self.data[i] = func(self.data[i + i], self.data[i + i + 1])

	def __delitem__(self, idx):
		self[idx] = self._default

	def __getitem__(self, idx):
		return self.data[idx + self._size]

	def __setitem__(self, idx, value):
		idx += self._size
		self.data[idx] = value
		idx >>= 1
		while idx:
			self.data[idx] = self._func(self.data[2 * idx], self.data[2 * idx + 1])
			idx >>= 1

	def __len__(self):
		return self._len

	def query(self, start, stop):
		if start == stop:
			return self.__getitem__(start)
		stop += 1
		start += self._size
		stop += self._size
		res = self._default
		while start < stop:
			if start & 1:
				res = self._func(res, self.data[start])
				start += 1
			if stop & 1:
				stop -= 1
				res = self._func(res, self.data[stop])
			start >>= 1
			stop >>= 1
		return res

	def __repr__(self):
		return 'SegmentTree({0})'.format(self.data)

class Factorial:

	def __init__(self, MOD):
		self.MOD = MOD
		self.factorials = [1, 1]
		self.invModulos = [0, 1]
		self.invFactorial_ = [1, 1]

	def calc(self, n):
		if n <= -1:
			print('Invalid argument to calculate n!')
			print('n must be non-negative value. But the argument was ' + str(n))
			exit()
		if n < len(self.factorials):
			return self.factorials[n]
		nextArr = [0] * (n + 1 - len(self.factorials))
		initialI = len(self.factorials)
		prev = self.factorials[-1]
		m = self.MOD
		for i in range(initialI, n + 1):
			prev = nextArr[i - initialI] = prev * i % m
		self.factorials += nextArr
		return self.factorials[n]

	def inv(self, n):
		if n <= -1:
			print('Invalid argument to calculate n^(-1)')
			print('n must be non-negative value. But the argument was ' + str(n))
			exit()
		p = self.MOD
		pi = n % p
		if pi < len(self.invModulos):
			return self.invModulos[pi]
		nextArr = [0] * (n + 1 - len(self.invModulos))
		initialI = len(self.invModulos)
		for i in range(initialI, min(p, n + 1)):
			next = -self.invModulos[p % i] * (p // i) % p
			self.invModulos.append(next)
		return self.invModulos[pi]

	def invFactorial(self, n):
		if n <= -1:
			print('Invalid argument to calculate (n^(-1))!')
			print('n must be non-negative value. But the argument was ' + str(n))
			exit()
		if n < len(self.invFactorial_):
			return self.invFactorial_[n]
		self.inv(n)
		nextArr = [0] * (n + 1 - len(self.invFactorial_))
		initialI = len(self.invFactorial_)
		prev = self.invFactorial_[-1]
		p = self.MOD
		for i in range(initialI, n + 1):
			prev = nextArr[i - initialI] = prev * self.invModulos[i % p] % p
		self.invFactorial_ += nextArr
		return self.invFactorial_[n]

class Combination:

	def __init__(self, MOD):
		self.MOD = MOD
		self.factorial = Factorial(MOD)

	def ncr(self, n, k):
		if k < 0 or n < k:
			return 0
		k = min(k, n - k)
		f = self.factorial
		return f.calc(n) * f.invFactorial(max(n - k, k)) * f.invFactorial(min(k, n - k)) % self.MOD

def powm(a, n, m):
	if a == 1 or n == 0:
		return 1
	if n % 2 == 0:
		s = powm(a, n // 2, m)
		return s * s % m
	else:
		return a * powm(a, n - 1, m) % m

def sort_list(list1, list2):
	zipped_pairs = zip(list2, list1)
	z = [x for (_, x) in sorted(zipped_pairs)]
	return z

def product(l):
	por = 1
	for i in range(len(l)):
		por *= l[i]
	return por

def binarySearchCount(arr, n, key):
	left = 0
	right = n - 1
	count = 0
	while left <= right:
		mid = int((right + left) / 2)
		if arr[mid] <= key:
			count = mid + 1
			left = mid + 1
		else:
			right = mid - 1
	return count

def countdig(n):
	c = 0
	while n > 0:
		n //= 10
		c += 1
	return c

def binary(x, length):
	y = bin(x)[2:]
	return y if len(y) >= length else '0' * (length - len(y)) + y

def countGreater(arr, n, k):
	l = 0
	r = n - 1
	leftGreater = n
	while l <= r:
		m = int(l + (r - l) / 2)
		if arr[m] >= k:
			leftGreater = m
			r = m - 1
		else:
			l = m + 1
	return n - leftGreater

class TrieNode:

	def __init__(self):
		self.children = [None] * 26
		self.isEndOfWord = False

class Trie:

	def __init__(self):
		self.root = self.getNode()

	def getNode(self):
		return TrieNode()

	def _charToIndex(self, ch):
		return ord(ch) - ord('a')

	def insert(self, key):
		pCrawl = self.root
		length = len(key)
		for level in range(length):
			index = self._charToIndex(key[level])
			if not pCrawl.children[index]:
				pCrawl.children[index] = self.getNode()
			pCrawl = pCrawl.children[index]
		pCrawl.isEndOfWord = True

	def search(self, key):
		pCrawl = self.root
		length = len(key)
		for level in range(length):
			index = self._charToIndex(key[level])
			if not pCrawl.children[index]:
				return False
			pCrawl = pCrawl.children[index]
		return pCrawl != None and pCrawl.isEndOfWord

class Node:

	def __init__(self, data):
		self.data = data
		self.count = 0
		self.left = None
		self.right = None

class BinaryTrie:

	def __init__(self):
		self.root = Node(0)

	def insert(self, pre_xor):
		self.temp = self.root
		for i in range(31, -1, -1):
			val = pre_xor & 1 << i
			if val:
				if not self.temp.right:
					self.temp.right = Node(0)
				self.temp = self.temp.right
				self.temp.count += 1
			if not val:
				if not self.temp.left:
					self.temp.left = Node(0)
				self.temp = self.temp.left
				self.temp.count += 1
		self.temp.data = pre_xor

	def query(self, xor):
		self.temp = self.root
		for i in range(31, -1, -1):
			val = xor & 1 << i
			if not val:
				if self.temp.left and self.temp.left.count > 0:
					self.temp = self.temp.left
				elif self.temp.right:
					self.temp = self.temp.right
			elif self.temp.right and self.temp.right.count > 0:
				self.temp = self.temp.right
			elif self.temp.left:
				self.temp = self.temp.left
			self.temp.count -= 1
		return xor ^ self.temp.data
(n, a1, b) = map(int, input().split())
l = []
ans = 0
d = defaultdict(int)
e = defaultdict(list)
e1 = defaultdict(int)
for i in range(n):
	(a, b, c) = map(int, input().split())
	d[b, c] += 1
for i in d:
	(b, c) = i
	e[c - a1 * b].append(d[i])
	e1[c - a1 * b] += d[i]
for i in e:
	for j in e[i]:
		ans += j * (e1[i] - j)
print(ans)


from sys import stdin
from collections import Counter

def f(line):
	return tuple((int(x) for x in line.split()[1:]))
(n, a, _) = [int(x) for x in stdin.readline().split()]
groups = Counter((f(line) for line in stdin.readlines()[:n]))
total = Counter()
for ((x, y), cnt) in groups.items():
	total[y - x * a] += cnt
ans = 0
for ((x, y), cnt) in groups.items():
	ans += cnt * max(0, total[y - x * a] - cnt)
print(ans)


import bisect
(n, a, b) = map(int, input().split(' '))
x = [0] * n
vx = [0] * n
vy = [0] * n
for i in range(n):
	(x[i], vx[i], vy[i]) = map(int, input().split(' '))
ntypes = dict()
for i in range(n):
	ntype = -a * vx[i] + 1 * vy[i]
	currSet = ntypes.get(ntype, set())
	currSet.add(i)
	ntypes[ntype] = currSet
result = 0
for key in ntypes.keys():
	currSet = ntypes[key]
	currLen = len(currSet)
	vtypes = dict()
	for i in currSet:
		vtype = vx[i] * 1 + vy[i] * a
		vtypes[vtype] = vtypes.get(vtype, 0) + 1
	for key in vtypes.keys():
		curr = vtypes[key]
		result += curr * (currLen - curr)
print(result)


from sys import stdin
from itertools import groupby

def f(line):
	t = line.split()
	return tuple((int(x) for x in t[1:]))
(n, a, b) = [int(x) for x in stdin.readline().split()]
points = sorted((f(line) for line in stdin.readlines()[:n]))
groups = [[k, len(tuple(g))] for (k, g) in groupby(points)]
total = {}
for ((x, y), cnt) in groups:
	if y - x * a not in total:
		total[y - x * a] = 0
	total[y - x * a] += cnt
ans = 0
for ((x, y), cnt) in groups:
	if y - x * a not in total:
		continue
	ans += cnt * (total[y - x * a] - cnt)
print(ans)


from collections import defaultdict
(n, a, b) = map(int, input().split())
res = defaultdict(lambda : 0)
t = defaultdict(lambda : 0)
ans = 0
for _ in range(n):
	(k, x, y) = map(int, input().split())
	ans += res[a * x - y] - t[x, y]
	res[a * x - y] += 1
	t[x, y] += 1
print(ans * 2)


import sys
import io, os
input = sys.stdin.buffer.readline
from collections import defaultdict
(n, a, b) = map(int, input().split())
XV = []
for i in range(n):
	(x, vx, vy) = map(int, input().split())
	XV.append((x, vx, vy))
if a != 0:
	ans = 0
	d = defaultdict(lambda : 0)
	dvx = defaultdict(lambda : 0)
	for (x, vx, vy) in XV:
		k = -a * vx + vy
		ans += max(0, d[k] - dvx[k, vx])
		d[k] += 1
		dvx[k, vx] += 1
	print(ans * 2)
else:
	ans = 0
	d = defaultdict(lambda : defaultdict(lambda : 0))
	ds = defaultdict(lambda : 0)
	for (x, vx, vy) in XV:
		ans += max(0, ds[vy] - d[vy][vx])
		d[vy][vx] += 1
		ds[vy] += 1
	print(ans * 2)


import sys
input = sys.stdin.readline
(n, a, b) = list(map(int, input().split()))
d1 = {}
d2 = {}
for i in range(n):
	c = input().split()[1:]
	t = a * int(c[0]) - int(c[1])
	if t in d1:
		d1[t] += 1
	else:
		d1[t] = 1
	t2 = ' '.join(c)
	if t2 in d2:
		d2[t2] += 1
	else:
		d2[t2] = 1
print(sum([x * (x - 1) for x in d1.values() if x >= 2]) - sum([x * (x - 1) for x in d2.values() if x >= 2]))