#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define x first
#define y second
#define rep1(i,l,r) for(register int i=l;i<=r;++i)
#define rep2(i,l,r) for(register int i=l;i>=r;--i)
#define rep3(i,x,y,z) for(register int i=x[y];~i;i=z[i])
#define rep4(i,x) for(auto i:x)
#define debug() puts("----------")
const int N=2e6+10;
const int inf=0x3f3f3f3f3f3f3f3f;
using namespace std;
int n,m,h[N],e[N],ne[N],w[N],id[N],idx,dist[2][N],rdl[N],rdr[N],cnt[N],kase;
bool vis[N];
struct Graph
{
int u,v;
int w;
}G[N];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f*x;
}
void add(int x,int y,int z,int d)
{
e[idx]=y;
w[idx]=z;
ne[idx]=h[x];
id[idx]=d;
h[x]=idx++;
return;
}
void dijkstra(int s,int d)
{
memset(vis,0,sizeof vis);
memset(dist[d],inf,sizeof dist[d]);
dist[d][s]=0;
priority_queue<pii> q;
q.push(make_pair(0,s));
while(!q.empty())
{
int x=q.top().y;
q.pop();
if(vis[x]) continue;
vis[x]=1;
rep3(i,h,x,ne)
{
int to=e[i];
if(dist[d][to]>dist[d][x]+w[i])
{
dist[d][to]=dist[d][x]+w[i];
q.push(make_pair(-dist[d][to],to));
}
}
}
return;
}
void dfs0()
{
memset(vis,0,sizeof vis);
queue<int> q;
q.push(1);
vis[1]=1;
while(!q.empty())
{
int t=q.front();
q.pop();
rep3(i,h,t,ne)
{
int to=e[i];
if(dist[0][to]==dist[0][t]+w[i]&&!vis[to])
{
if(!rdl[to]) rdl[to]=rdl[t];
vis[to]=1;
q.push(to);
}
}
}
return;
}
void dfs1()
{
memset(vis,0,sizeof vis);
queue<int> q;
q.push(n);
while(!q.empty())
{
int t=q.front();
q.pop();
rep3(i,h,t,ne)
{
int to=e[i];
if(dist[1][to]==dist[1][t]+w[i]&&!vis[to])
{
if(!rdr[to])
{
rdr[to]=rdr[t];
}
vis[to]=1;
q.push(to);
}
}
}
return;
}
struct Segment_Tree
{
struct node
{
int l,r;
int Min;
int tag;
}tr[N<<2];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){tr[p].Min=min(tr[ls(p)].Min,tr[rs(p)].Min);}
void build(int p,int l,int r)
{
tr[p]={l,r,inf,inf};
if(l==r) return;
int mid=l+r>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
push_up(p);
return;
}
void push_down(int p)
{
tr[ls(p)].Min=min(tr[ls(p)].Min,tr[p].tag);
tr[rs(p)].Min=min(tr[rs(p)].Min,tr[p].tag);
tr[ls(p)].tag=min(tr[ls(p)].tag,tr[p].tag);
tr[rs(p)].tag=min(tr[rs(p)].tag,tr[p].tag);
tr[p].tag=inf;
return;
}
void update(int p,int l,int r,int k)
{
if(tr[p].l>=l&&tr[p].r<=r)
{
tr[p].Min=min(tr[p].Min,k);
tr[p].tag=min(tr[p].tag,k);
return;
}
push_down(p);
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid) update(ls(p),l,r,k);
if(r>mid) update(rs(p),l,r,k);
push_up(p);
return;
}
int query(int p,int x)
{
if(tr[p].l==tr[p].r) return tr[p].Min;
push_down(p);
int mid=tr[p].l+tr[p].r>>1;
if(x<=mid) return query(ls(p),x);
else return query(rs(p),x);
}
}seg;
signed main()
{
memset(h,-1,sizeof h);
n=read();
m=read();
rep1(i,1,m)
{
G[i*2-1].u=G[i*2].v=read();
G[i*2-1].v=G[i*2].u=read();
G[i*2-1].w=G[i*2].w=read();
add(G[i*2-1].u,G[i*2-1].v,G[i*2-1].w,i*2-1);
add(G[i*2].u,G[i*2].v,G[i*2].w,i*2);
}
dijkstra(1,0);
dijkstra(n,1);
if(dist[0][n]>=inf) return 0;
rdl[1]=rdr[1]=-1;
cnt[0]=0;
int now=1;
while(now!=n)
{
rep3(i,h,now,ne)
{
int to=e[i];
if(dist[1][now]==dist[1][to]+w[i])
{
rdl[to]=rdr[to]=id[i];
cnt[id[i]]=++kase;
now=to;
break;
}
}
}
dfs0();
dfs1();
rep1(i,1,n)
{
if(rdl[i]==-1) rdl[i]=0;
if(rdr[i]==-1) rdr[i]=0;
}
seg.build(1,1,kase);
rep1(i,1,2*m)
{
if(rdl[G[i].v]==i) continue;
int L=cnt[rdl[G[i].u]]+1,R=cnt[rdr[G[i].v]];
if(L>R) continue;
int dis=dist[0][G[i].u]+G[i].w+dist[1][G[i].v];
seg.update(1,L,R,dis);
}
int Min=-inf,cnt=0;
rep1(i,1,kase)
{
int dis=seg.query(1,i);
if(Min<dis)
{
Min=dis;
cnt=1;
}
else if(Min==dis) ++cnt;
}
cout<<Min<<' ';
if(Min==dist[0][n]) cout<<m<<"\n";
else cout<<cnt<<"\n";
return 0;
}