#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define debug cerr << "-- " << __LINE__ << " --" << endl
#define MOD 1000000007
#define ALL(o) (o).begin(), (o).end()
typedef long long int ll;

using namespace std;
typedef vector<int> vi;
typedef vector<vi> vvi;
template <typename T> string to_string(const vector<T> &xs) {string s="["; for(int i=0;i<xs.size();i++) s += (i>0 ? " " : "") + to_string(xs[i]); return s + "]";}
template <typename T> ll argmax(const T &xs) {return distance(xs.begin(), max_element(xs.begin(), xs.end()));}
template <typename T> ll argmin(const T &xs) {return distance(xs.begin(), min_element(xs.begin(), xs.end()));}
ll nextLL(){ll x; scanf("%lld", &x); return x;}
int nextInt(){int x; scanf("%ld", &x); return x;}
string nextString(){string s; cin >> s; return s;}


ll extgcd(ll a, ll b, ll &x, ll &y) {
  ll g = a; x = 1; y = 0;
  if (b != 0) g = extgcd(b, a % b, y, x), y -= (a / b) * x;
  return g;
}
ll gcd(ll a, ll b) { ll x, y; return extgcd(a, b, x, y);}

class llm{
	ll n;
public:
	llm(){n = 0;}
	llm(ll n){this->n = (n >= 0) ? (n % MOD) : (MOD - 1 - (-n-1 % MOD));}
	operator ll() const {return n;}
	llm operator+(const llm &y) const{ return llm((ll)*this + (ll)y);}
	llm operator+=(const llm &y) { *this = *this + y; return *this;}
	llm operator-(const llm &y) const{ return llm((ll)*this - (ll)y);}
	llm operator-=(const llm &y) { *this = *this - y; return *this;}
	llm operator-() const{ return llm(0) - *this;}
	llm operator*(const llm &y) const{ return llm((ll)*this * (ll)y);}
	llm operator*=(const llm &y) { *this = *this * y; return *this;}
	llm operator/(const llm &y) const{ return *this * y.inv();}
	llm operator/=(const llm &y) { *this = *this / y; return *this;}
	llm inv() const{
		ll x, y;
		if (extgcd(n, MOD, x, y) == 1) return llm(x);
  		return llm(0); // unsolvable
	}
	friend istream& operator >> (istream& os, llm& x);
	friend ostream& operator << (ostream& os, const llm& x);
};
istream& operator >> (istream& is, llm& x){ll n; is >> n; x = llm(n); return is;}
ostream& operator << (ostream& os, const llm& x){os << (ll)x; return os;}

template<typename T> T comb(ll n, ll k){
	T ret = 1;
	for(ll i=1;i<=k;i++) ret = ret * T(n - k + i) / T(i);
	return ret;
}

class Tree{
	/*
		根つき木．LCAなど
		静的な根つき木に関する情報を色々計算してくれる
	*/
public:
	// 一般のグラフ用の変数
	int V; // 頂点数
	int E; // 辺数
	vector<vector<ll>> adjList; // 隣接リスト．
	map<pair<ll, ll>, ll> adjMat; // 隣接行列（疎）

	// 根つき木用の変数
	int root = -1; // 「根」とみなす頂点番号．
	vector<vector<ll>> ancestor; // ダブリング先祖．[i][0] で i の親，[i][1] で親^2，[i][2] で親^4 が取れる．親は parent(i) でも取得可
	vector<vector<ll>> children; // 子供の一覧
	vector<ll> nextSibling; // 次のきょうだい．存在しない場合は -1
	vector<ll> numDescendant; // 子孫の個数
	vector<ll> depth; // 深さ（辺数で測った根からの距離）
	vector<ll> farness; // 遠さ（コストで測った根からの距離）
	vector<ll> codepth; // 残りの深さ（自身からその子孫までの最大深さ．[root] での codepth は全体の最大深さに一致）
	vector<ll> numLeaf; // 自身およびその子孫のうち葉の個数
	vector<ll> preOrder; // dfs した時の行きがけ順
	vector<ll> preOrderIndex; // dfs した時の行きがけ順のインデックス
	vector<ll> postOrder; // dfs した時の帰りがけ順
	vector<ll> postOrderIndex; // dfs した時の帰りがけ順のインデックス

	Tree(){};
	Tree(const vector<ll> &st, const vector<ll> &ed, const vector<ll> &cost, ll root){
		/*
			st, ed, root は 0-indexed．辺の向きは問わない．
			辺数 E と頂点数 V は辺の数から推論される．頂点番号に抜けがあってはならない．
			cost は省略可能（移譲コンストラクタによる．省略された場合，全辺のコストは 1 となる）

			引数に渡された辺情報が木になっていない場合の動作は保障されない．
			（例えば多重辺があったり自己ループがあったり・・・）

			LCA 用の前計算などするので初期化に O(V log V) かかる．
			（逆に言えば O(V log V）で可能な前計算はここに色々詰め込みたい）
		*/
		assert(st.size() == ed.size());
		E = st.size();
		V = st.size() + 1;

		// adjList と adjMat を作成
		adjList = vector<vector<ll>>(V);
		for(int i=0;i<E;i++){
			assert(0 <= st[i] && st[i] < V);
			assert(0 <= ed[i] && ed[i] < V);
			assert(st[i] != ed[i]);
			adjList[st[i]].push_back(ed[i]);
			adjList[ed[i]].push_back(st[i]);
			adjMat.insert(make_pair(make_pair(st[i], ed[i]), cost[i]));
			adjMat.insert(make_pair(make_pair(ed[i], st[i]), cost[i]));
		}

		// root から BFS して depth と farness と children と ancestor を計算
		this->root = root;
		depth = vector<ll>(V, -1);
		farness = vector<ll>(V, -1);
		children = vector<vector<ll>>(V);
		ancestor = vector<vector<ll>>(V);
		depth[root] = 0;
		farness[root] = 0;
		queue<ll> q; q.push(root);
		while(!q.empty()){
			ll now = q.front(); q.pop();
			for(auto &next : adjList[now]){
				if (depth[next] >= 0) continue;
				depth[next] = depth[now] + 1;
				farness[next] = farness[now] + adjMat[make_pair(now, next)];
				q.push(next);
				children[now].push_back(next);
				ancestor[next].push_back(now); // parent
			}
			for(ll i=1;;i++){
				if ((1 << i) > depth[now]) break;
				ancestor[now].push_back(ancestor[ancestor[now][i - 1]][i - 1]);
			}			
		}

		// nextSibling の計算
		nextSibling = vector<ll>(V, -1);
		for(auto &c : children)for(int j=0;j+1<c.size();j++){
			nextSibling[c[j]] = c[j+1];
		}

		// root から dfs して preOrder, postOrder, numDescendant を計算
		preOrder = vector<ll>();
		postOrder = vector<ll>();
		preOrderIndex = vector<ll>(V);
		postOrderIndex = vector<ll>(V);
		numDescendant = vector<ll>(V);
		codepth = vector<ll>(V);
		numLeaf = vector<ll>(V);
		_computeWithDFS(root);
	}
	// コストが省略された場合の移譲コンストラクタ
	Tree(const vector<ll> &st, const vector<ll> &ed, ll root): Tree(st, ed, vector<ll>(st.size(), 1), root){}

	void _computeWithDFS(ll idx){
		preOrderIndex[idx] = preOrder.size(); preOrder.push_back(idx);
		numDescendant[idx] = 0;
		codepth[idx] = 0;
		numLeaf[idx] = 0;
		for(auto &c : children[idx]){
			_computeWithDFS(c); // recursion
			numDescendant[idx] += 1 + numDescendant[c];
			codepth[idx] = max(codepth[idx], 1 + codepth[c]);
			numLeaf[idx] += numLeaf[c];
		}
		if (numLeaf[idx] == 0) numLeaf[idx] = 1;
		postOrderIndex[idx] = postOrder.size(); postOrder.push_back(idx);
	}

	void _dfs(ll idx){ // dfs のテンプレート．_dfs(root) から再帰開始
		// preOrder
		for(auto &c : children[idx]) _dfs(c);
		// postOrder
	}

	ll parent(ll idx) {
		/* 頂点 idx の親を返す．存在しない場合（i.e. idx が根の場合）は -1 */
		return (ancestor[idx].size() > 0) ? ancestor[idx][0] : -1;
	}

	ll nthAncestor(ll idx, ll n){
		/*
			頂点 idx から親を n 回辿った先祖を求める．O(log n)
			n==0 の場合は idx が返る．
			存在しない場合は -1 が返る．
		*/
		assert(0 <= idx && idx < V);
		assert(0 <= n);
		if (depth[idx] < n) return -1;
		if (n == 0) return idx;
		ll p = 63LL - __builtin_clzll(n);
		return nthAncestor(ancestor[idx][p], n - (1LL << p));
	}

	ll LCA(ll a, ll b){
		/*
			頂点 a と b の最小共通祖先(LCA)を求める．O(log V)
		*/
		assert(0 <= a && a < V);
		assert(0 <= b && b < V);
		ll ok = 0, ng = min(depth[a], depth[b]) + 1;
		while (ng - ok > 1){
			ll mid = (ok + ng) / 2;
			if (nthAncestor(a, depth[a] - mid) == nthAncestor(b, depth[b] - mid)){
				ok = mid;
			}else{
				ng = mid;
			}
		}
		return nthAncestor(a, depth[a] - ok);
	}


	ll distance(ll a, ll b){ // 辺数としての距離
		ll lca = LCA(a, b);
		return depth[a] + depth[b] - 2 * depth[lca];
	}
	ll fare(ll a, ll b){ // コストとしての距離
		ll lca = LCA(a, b);
		return farness[a] + farness[b] - 2 * farness[lca];
	}

};


ll N;
vector<ll> a, b;


ll computeMinLeaf(ll c){
	Tree tr(a, b, c);
	/*
	vector<ll> nl;
	for(ll i : tr.children[c]) nl.pb(tr.numLeaf[i]);
	return *max_element(ALL(nl)) * nl.size();
	*/
	vector<ll> maxBranch(N, 1);
	for(ll i=0;i<N;i++){
		ll d = tr.depth[i];
		maxBranch[d] = max(maxBranch[d], (ll) tr.children[i].size());
	}
	ll ret = 1LL;
	for(ll i : maxBranch) ret *= i;
	return ret;
}
ll computeMinLeaf2(ll c1, ll c2){
	Tree tr(a, b, c1);
	vector<ll> maxBranch(N, 1);
	for(ll i=0;i<N;i++){
		if (tr.LCA(c2, i) == c1){
			ll d = tr.depth[i];
			if (d == 0) maxBranch[d] = max(maxBranch[d], ((ll) tr.children[i].size())-1);
			else maxBranch[d] = max(maxBranch[d], (ll) tr.children[i].size());
		}else{
			ll d = tr.depth[i] - 1;
			maxBranch[d] = max(maxBranch[d], (ll) tr.children[i].size());
		}
	}
	ll ret = 2LL;
	for(ll i : maxBranch) ret *= i;
	return ret;
	/*
	vector<ll> nl1, nl2;
	ll maxnl = 0;
	for(ll i : tr.children[c1]){
		if (i == c2) continue;
		nl1.pb(tr.numLeaf[i]);
		maxnl = max(maxnl, tr.numLeaf[i]);
	}
	if (nl1.empty()) {nl1.pb(1); maxnl = max(maxnl, 1LL);}

	for(ll i : tr.children[c2]){
		nl2.pb(tr.numLeaf[i]);
		maxnl = max(maxnl, tr.numLeaf[i]);
	}
	if (nl2.empty()) {nl2.pb(1); maxnl = max(maxnl, 1LL);}
	return maxnl * max(nl1.size(), nl2.size()) * 2;
	*/
}


int main(void){
	cin >> N;
	for(int i=1;i<N;i++){
		a.pb(nextLL() - 1);
		b.pb(nextLL() - 1);
	}	

	// 中心を調べる
	Tree tr = Tree(a, b, 0);
	ll _ = argmax(tr.depth);
	Tree tr2 = Tree(a, b, _);
	ll __ = argmax(tr2.depth);
	Tree tr3 = Tree(a, b, __);
	ll diameter = *max_element(ALL(tr2.depth));
	vector<ll> centers;
	for(int i=0;i<tr2.depth.size();i++){
		if (diameter%2 && llabs(diameter - tr2.depth[i]*2)==1 && llabs(diameter - tr3.depth[i]*2)==1){
			centers.pb(i);
		}else if (diameter%2==0 && diameter/2 == tr2.depth[i] && diameter/2 == tr3.depth[i]){
			centers.pb(i);
		}
	}
	// cerr << to_string(centers) << endl;

	ll minLeaf = LLONG_MAX;
	if (centers.size()==2){
		// 中心が2つの場合
		minLeaf = min(minLeaf, computeMinLeaf2(centers[0], centers[1]));
	}else{
		// 中心が1つの場合
		minLeaf = min(minLeaf, computeMinLeaf(centers[0]));
		for(ll i : tr3.adjList[centers[0]]){
			minLeaf = min(minLeaf, computeMinLeaf2(centers[0], i));			
		}
	}


	cout << diameter/2 + 1 << " " << minLeaf << endl;
	
    return 0;
}




Measured memory_bytes: 384