////////////////////////////////////////////////////////////////////
// This source code is for Visual Studio
////////////////////////////////////////////////////////////////////
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
#include <sstream>
#include <iterator>
#include <stack>
#include <functional>
#include <iomanip>
#include <string>
#include <cstring>
#include <deque>
#include <math.h>
#include <assert.h>

#define	numberof(a)	(sizeof(a) / sizeof(a[0]))
#define	INF32		INT32_MAX
#define	INF64		INT64_MAX
#define Rep(i,n) for(int i = 0; i < (n); ++i )

using namespace std;

typedef vector< vector<int> > MAT;
typedef pair<int, int> PINT;
typedef long long ll;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t s32;
typedef int64_t s64;

//constant
//--------------------------------------------
const double EPS = 1e-10;
const double PI  = acos(-1.0);


//////////////////////////
// dijkstra(2点間最短経路探索)
// http://qiita.com/intatonix/items/8a50556697206ee89bcd
// O(ElogV) (E:辺の数、V:頂点数)
// 
// @param[in] G[i][j]:ノードiにつながっているj番目のノード(firstがノード番号, secondがコスト。つながっているノードだけ含まれる)
// @param[in] startNode:起点ノード番号
// @retval : 起点からノードiへの最短経路の重み、INFで初期化
vector<ll> reverse_path;
vector<ll> dijkstra(vector< vector< pair<ll, ll> > > & G, ll startNode)
{
	/*
	(例)
	vector< vector< pair<ll, ll> > > G(N);// G[i][j]:ノードiにつながっているj番目のノード(firstがノード番号, secondがコスト)
	Rep(i, N - 1) {	// 各辺
	ll u = 0;
	ll v = 0;
	ll dist = 0;

	cin >> u >> v >> dist;

	// create graph
	G[u].push_back( make_pair(v, C) );
	G[v].push_back( make_pair(u, C) );
	}
	...
	vector<ll> result = dijkstra(G, 0);
	*/
	priority_queue< pair<ll, ll> > searchPQ;	// PQの要素はこれから探索するノード(firstがコスト, secondがノード番号)
	const ll V_MAX = G.size();// vertex number
	vector<bool> isChecked(V_MAX, false);	// trueのときは探索済
	vector<ll> output_dist(V_MAX);
	reverse_path.resize(V_MAX); /*array with links to parents*/

	Rep(i, V_MAX) {
		output_dist[i] = INF64;
		isChecked[i] = false;
	}

	// 起点からスタート
	output_dist[startNode] = 0;
	searchPQ.push(make_pair(0, startNode));

	while (!searchPQ.empty())
	{
		pair<ll, ll> search_node = searchPQ.top(); searchPQ.pop();
		ll u = search_node.second;// 調べるノード
		isChecked[u] = true;

		if (output_dist[u] < search_node.first * (-1)) continue;// search_node.firstは負の値にしている

		for (ll j = 0; j < G[u].size(); j++) {
			// uにつながっているノードを探索
			ll v = G[u][j].first;
			if (isChecked[v] == true) continue;
			if (output_dist[v] > output_dist[u] + G[u][j].second) {
				output_dist[v] = output_dist[u] + G[u][j].second;
				reverse_path[v] = u;
				searchPQ.push(make_pair(output_dist[v] * (-1), v));//priority_queueはデフォルトで大きい値をpopしてしまうのでマイナスをかける
			}
		}
	}

	return output_dist;
}

// 最短経路を復元
vector<ll> recoverPath(vector<ll> reverse_path, ll src, ll dest) {
	vector<ll> path;
	ll v = dest;

	while (v != src) {
		path.push_back(v);
		v = reverse_path[v];
	}
	path.push_back(src);
	std::reverse(path.begin(), path.end());
	return path;
}

// main function
int main()
{
	ll N = 0;
	ll M = 0;
	ll ans = 0;

	cin >> N >> M;
	vector< vector< pair<ll, ll> > > G(N);// G[i][j]:ノードiにつながっているj番目のノード(firstがノード番号, secondがコスト)
	map< pair<ll, ll>, bool > isFoundEdge;
	map< pair<ll, ll>, bool >::iterator itr;

	Rep(i, M) {	// 各辺
		ll a = 0;
		ll b = 0;
		ll c = 0;

		cin >> a >> b >> c;

		// create graph
		a--;
		b--;
		G[a].push_back(make_pair(b, c));
		G[b].push_back(make_pair(a, c));
		
		if(a > b) isFoundEdge[make_pair(b, a)] = false;
		else isFoundEdge[make_pair(a, b)] = false;
	}

	// solve
	vector<ll> dist;
	vector<ll> total_minPath;
	Rep(i, N) {
		dist = dijkstra(G, i);

		Rep(j, N) {
			// from i to j's minimal path
			if (i == j) continue;
			total_minPath = recoverPath(reverse_path, i, j);

			pair<ll, ll> edge;
			pair<ll, ll> tmp;
			edge.first = total_minPath[0];
			for (ll k = 1; k < total_minPath.size(); ++k) {
				edge.second = total_minPath[k];
				if (edge.first <= edge.second) {
					tmp = edge;
				}
				else {
					tmp.first = edge.second;
					tmp.second = edge.first;
				}
				isFoundEdge[tmp] = true;

				// next
				edge.first = total_minPath[k];
			}
		}
	}

	for (itr = isFoundEdge.begin(); itr != isFoundEdge.end(); itr++) {
		if (itr->second == false) {
			ans++;
		}
	}

	cout << ans << endl;

	return 0;
}

Measured memory_bytes: 384