using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System;
using System.Numerics;
using static System.Math;
using static System.Console;
class E { static void Main() => new J(); }
class J
{
IEnumerable<int> Hoge()
{
while (true) yield return 1;
}
int F() => int.Parse(ReadLine());
long FL() => int.Parse(ReadLine());
int[] G() => ReadLine().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
long[] GL() => ReadLine().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries).Select(long.Parse).ToArray();
public const int MOD = 1000000007;
public J()
{
SetOut(new StreamWriter(OpenStandardOutput()) { AutoFlush = false });
Solve();
Out.Flush();
}
int V;
List<Tuple<int,int>>[]es;
void Solve()
{
var I = G();
V = I[0];
int E = I[1], R = I[2], T = I[3];
es = new List<Tuple<int, int>>[V];
for (var i = 0; i < V; i++) es[i] = new List<Tuple<int, int>>();
for (var e = 0; e < E; e++)
{
I = G();
int a = I[0] - 1, b = I[1] - 1, c = I[2];
es[a].Add(new Tuple<int, int>(b, c));
es[b].Add(new Tuple<int, int>(a, c));
}
var s = 0L;
for (var u = 0; u < V; u++)
{
var d = DijkstraFrom(u);
Array.Sort(d);
var i = 1;
for (var x = 1; x < V; x++)
{
while (i < V && R * d[x] >= T * d[i]) i++;
s += V - i;
}
}
Console.WriteLine(s);
}
int[] DijkstraFrom(int u)
{
var d = Enumerable.Repeat(MOD, V).ToArray();
var queue = new PriorityQueue<Tuple<int, int>>((x, y) => x.Item2.CompareTo(y.Item2));
d[u] = 0;
queue.Enqueue(new Tuple<int, int>(u, 0));
while (queue.Count > 0)
{
var p = queue.Dequeue();
var v = p.Item1;
if (d[v] < p.Item2) continue;
foreach (var e in es[v])
{
var tmp = d[v] + e.Item2;
if (d[e.Item1] > tmp) queue.Enqueue(new Tuple<int, int>(e.Item1, d[e.Item1] = tmp));
}
}
return d;
}
long Pow(long a, long b, long mod)
{
var p = 1L;
var x = a;
while (b > 0)
{
if ((b & 1) == 1) p = (p * x) % mod;
b >>= 1;
x = (x * x) % mod;
}
return p;
}
long GCD(long a, long b)
{
var n = (ulong)Math.Abs(a); var m = (ulong)Math.Abs(b);
if (n == 0) return (long)m; if (m == 0) return (long)n;
int zm = 0, zn = 0;
while ((n & 1) == 0) { n >>= 1; zn++; }
while ((m & 1) == 0) { m >>= 1; zm++; }
while (m != n)
{
if (m > n) { m -= n; while ((m & 1) == 0) m >>= 1; }
else { n -= m; while ((n & 1) == 0) n >>= 1; }
}
return (long)n << Math.Min(zm, zn);
}
int FirstBinary(int min, int max, Predicate<int> pred)
{
while (min < max)
{
var mid = (min + max) / 2;
if (pred(mid)) max = mid;
else min = mid + 1;
}
return min;
}
Tuple<long, long> SolveLinear(long n, long m)
{
if (n < 0) { var p = SolveLinear(-n, m); return p == null ? p : new Tuple<long, long>(-p.Item1, p.Item2); }
if (m < 0) { var p = SolveLinear(n, -m); return p == null ? p : new Tuple<long, long>(p.Item1, -p.Item2); }
if (n < m) { var p = SolveLinear(m, n); return p == null ? p : new Tuple<long, long>(p.Item2, p.Item1); }
long a = 1, b = 0, c = 0, d = 1;
while (m > 0)
{
var r = n % m;
var q = n / m;
n = m;
m = r;
var tmp = a;
a = -a * q + b;
b = tmp;
tmp = c;
c = -c * q + d;
d = tmp;
}
return n != 1 ? null : new Tuple<long, long>(d, b);
}
long Inverse(long a, long mod)
{
if (a < 0) { a %= mod; if (a < 0) a += mod; }
var t = SolveLinear(a, mod);
return t.Item1 > 0 ? t.Item1 : t.Item1 + mod;
}
}
class Deque<T>
{
T[] array;
int offset, capacity;
public int Count { get; protected set; }
public Deque(int capacity) { array = new T[this.capacity = capacity]; Count = 0; offset = 0; }
public Deque() : this(16) { }
public T this[int index] { get { return array[GetIndex(index)]; } set { array[GetIndex(index)] = value; } }
int GetIndex(int index) { var tmp = index + offset; return tmp >= capacity ? tmp - capacity : tmp; }
public T PeekFront() => array[offset];
public T PeekBack() => array[GetIndex(Count - 1)];
public void PushFront(T item)
{
if (Count == capacity) Extend();
if (--offset < 0) offset += array.Length;
array[offset] = item;
Count++;
}
public T PopFront()
{
Count--;
var tmp = array[offset++];
if (offset >= capacity) offset -= capacity;
return tmp;
}
public void PushBack(T item)
{
if (Count == capacity) Extend();
var id = (Count++) + offset;
if (id >= capacity) id -= capacity;
array[id] = item;
}
public T PopBack() => array[GetIndex(--Count)];
public void Insert(int index, T item)
{
PushFront(item);
for (var i = 0; i < index; i++) this[i] = this[i + 1];
this[index] = item;
}
public T RemoveAt(int index)
{
var tmp = this[index];
for (var i = index; i > 0; i--) this[i] = this[i - 1];
PopFront();
return tmp;
}
void Extend()
{
var newArray = new T[capacity << 1];
if (offset > capacity - Count)
{
var length = array.Length - offset;
Array.Copy(array, offset, newArray, 0, length);
Array.Copy(array, 0, newArray, length, Count - length);
}
else Array.Copy(array, offset, newArray, 0, Count);
array = newArray;
offset = 0;
capacity <<= 1;
}
}
class PriorityQueue<T>
{
Comparison<T> comp;
List<T> list = new List<T>();
public int Count;
public PriorityQueue(Comparison<T> c, IEnumerable<T> source = null)
{
comp = c;
if (source != null) foreach (var x in source) Enqueue(x);
}
public void Enqueue(T x)
{
var pos = Count++;
list.Add(x);
while (pos > 0)
{
var p = (pos - 1) / 2;
if (comp(list[p], x) <= 0) break;
list[pos] = list[p];
pos = p;
}
list[pos] = x;
}
public T Dequeue()
{
var value = list[0];
var x = list[--Count];
list.RemoveAt(Count);
if (Count == 0) return value;
var pos = 0;
while (pos * 2 + 1 < Count)
{
var a = 2 * pos + 1;
var b = 2 * pos + 2;
if (b < Count && comp(list[b], list[a]) < 0) a = b;
if (comp(list[a], x) >= 0) break;
list[pos] = list[a];
pos = a;
}
list[pos] = x;
return value;
}
public T Peek() => list[0];
public bool IsEmpty => Count == 0;
}
class UnionFindTree
{
int N;
int[] parent, rank;
public UnionFindTree(int capacity)
{
N = capacity;
parent = new int[N];
rank = new int[N];
for (var i = 0; i < N; i++) parent[i] = i;
}
public int GetRootOf(int x) => parent[x] == x ? x : parent[x] = GetRootOf(parent[x]);
public bool UniteCategory(int x, int y)
{
if ((x = GetRootOf(x)) == (y = GetRootOf(y))) return false;
if (rank[x] < rank[y]) parent[x] = y;
else
{
parent[y] = x;
if (rank[x] == rank[y]) rank[x]++;
}
return true;
}
public bool IsSameCategory(int x, int y) => GetRootOf(x) == GetRootOf(y);
}