#include <stdio.h>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
typedef struct Edge { //边结点的信息
char u;
char v;
int weight;
Edge(char u1, char v1, int weight1) { //构造函数
u = u1;
v = v1;
weight = weight1;
}
};
map<char, char>
father; //字符/字符串作为顶点编号需要用map才能实现!
char FindDisjointSet(char u) { //压缩路径查找和初始化一起进行
if (father.find(u) != father.end()) { // u结点的已经初始化的情况下
if (father[u] != u) { //非根结点
father[u] = FindDisjointSet(
father[u]); //将当前结点的父结点直接指向当前集合的根!
}
} else { // u结点的未初始化的情况下 其父亲结点等于本身
father[u] = u;
}
return father[u]; //返回u结点的父节点(根)
}
void UnionDisjointSet(char u, char v) { //将结点v合并到结点u 顶点
char uroot = FindDisjointSet(u); //寻找u的根(最上面的祖先结点)
char vroot = FindDisjointSet(v);//寻找v的根(最上面的祖先结点)
father[vroot] = uroot; //v的父亲结点指向u结点
}
bool compare( Edge lhs, Edge rhs) { //按权值进行排序
return lhs.weight < rhs.weight;
}
int main() {
int n ;
while (scanf("%d", &n) != EOF) {
if ( n == 0 ) {
break;
}
father.clear(); //每次重新开始后father哈希表的数据要清空需重新初始化!
int total = 0; //每次结束后要重新赋值!
int CountEdgeNum = 0;//每次结束后要重新赋值!
vector<Edge> edge;
for ( int i = 0 ; i < n - 1 ;
i++) { //注意int和char类型的输入要用空格隔开!
char u;
int k;
scanf(" %c %d", &u, &k); //首个顶点及道路数
if ( k > 0) {
for (int j = 0 ; j < k ; j++) {
char v;
int cost;
scanf(" %c %d", &v, &cost);
Edge e(u, v, cost); //保存边结点
edge.push_back(e); //存入动态数组
}
} else {
continue;
}
}
//按权值(费用)排序边表数组
sort(edge.begin(), edge.end(), compare);
for (int i = 0 ; i < edge.size() ; i++) { //遍历整个边表数组
//取出每个结点的编号
char u = edge[i].u;
char v = edge[i].v;
int cost = edge[i].weight;
if (FindDisjointSet(u) != FindDisjointSet(v)) { //不在一个集合
UnionDisjointSet(u, v); //按最小生成树的克鲁斯卡尔算法合并
CountEdgeNum++; //边数+1
total += cost; //费用累加
if ( CountEdgeNum == n -
1) { //提前结束循环,当满足树的条件时边数 = 顶点数-1
break;
}
}
}
printf("%d\n", total);
}
return 0;
}