目录
问题描述
谷同学很喜欢玩计算机游戏,特别是战略游戏,但是有时他不能尽快找到解所以常常感到很沮丧。现在面临如下问题:他必须在一个中世纪的城堡里设防,城堡里的道路形成一棵无向树。要在结点上安排最少的士兵使得他们可以看到所有边。你能帮助他吗?
你的任务是给出士兵的最少数目。
输入:包含多组数据。每组数据表示一棵树,在每组数据中:
第一行是结点的数目。
接下来的几行,每行按如下格式描述一个结点:结点标识符 : ( 道路的数目 ) 结点标识符1 结点标识符2 ...... 结点标识符道路的数目或者结点标识符 : (0)
对于 n (0<n<=1500) 个结点,结点标识符是一个从 0 到 n - 1 的整数。每条边在测试用例中只出现一次。
对于每组数据,各给出一个整数表示士兵的最少数目。
测试输入 | 期待的输出 | 时间限制 | 内存限制 | 额外进程 |
---|---|---|---|---|
测试用例 1 | 以文本方式显示
| 以文本方式显示
|
问题分析
这道题的思路比较简单。我们最先想到的一种解法就是按结点度数从大到小排列,每遍历到一个结点就把这个结点和与它相关的所有边都删除,然后更新剩余结点度数。直到边数为0.
但是这种想法显然在存储、遍历和删除时都会很麻烦。
于是重新考虑题目解法。一开始我进入了一个误区,认为所有度数大于等于2的结点都要放置一个士兵。但是后来想到一种情况:
这样的话1、2、4的度都>=2,但是其实只需要放置2和4就可以链接所有边。
根据以上分析,仔细考虑题目,我们可以发现,实际上我们可以从叶子结点所对应的父结点着手开始考虑。
即,如果我们能先找到一个叶子结点,然后把士兵放在其父结点上,把它的父结点连接的所有边删除,把父节点也删除。下一轮继续寻找叶子结点,重复上述操作。这样我们实际上就较为顺畅地实现了最开始的那种解法。
问题解决
int const MM=1501; //定义最大结点数
int map[MM][MM]; //定义图,存储各个顶点有无边
int degree[MM]; //定义记录每个结点的度的数组
int main() {
int n;
while (cin>>n) //有输入才开始本轮循环
{
for (int i = 0; i < n; i++) //读入数据
{
int index; //先读入结点下标
cin>>index;
getchar();getchar(); //吸收:和(
int d;
cin >> d; //读入该结点所连的边数
getchar(); //吸收)
degree[index]+=d; //更新该结点的度
for (int j = 0; j < d; j++) //依次读入该结点所连边的另一端结点的下标
{
getchar(); //吸收下标前的空格
int x;
cin>>x; //读入另一端的结点下标
map[index][x]=1; //在图中标记这两个点之间有边,无向图要标记两次
map[x][index]=1;
degree[x]++; //另一端结点度数也++
}
getchar(); //吸收回车
}
int ans = 0,sum=0; //初始化士兵数ans
while(sum<n) //sum是用来记录有多少个结点被访问到了,都被访问到后不再进入循环
{
for (int i = 0; i < n; i++)
{
if (degree[i] == 1) //找到度数为1的结点,即叶子结点
{
degree[i]=0; //父结点连线去掉后该叶子结点度数肯定为0,此处先行更新
sum++; //叶子结点已遍历,所以已遍历结点数++
int j;
for(j=0;j<n;j++) //寻找该叶子结点的父结点
{
if(map[i][j]==1) //有边证明父结点找到
{
ans++;
map[j][i]=0; //去掉该边
map[i][j]=0;
degree[j]=0; //父结点度数也归0
sum++; //父结点已遍历到,所以已遍历结点数++
break; //找到父结点后退出循环,j即为父结点下标
}
}
for(int k=0;k<n;k++) //遍历父结点所连的边
{
if(map[j][k]==1) //如果某结点与该父结点有边
{
map[j][k]=0; //删除该边
map[k][j]=0;
if(degree[k]==1) //如果当前遍历到的某结点也是叶结点
{
degree[k]=0; //度归0
sum++; //该叶子结点已遍历,所以已遍历结点数++
}
else
{
degree[k]--; //否则度--,因为度不为0之后还得遍历这个结点
}
}
}
}
}
}
cout<<ans<<endl; //输出最少士兵个数
}
return 0;
}
其中需要注意的是,每轮先找到一个叶子结点,在找这个叶子结点的父结点时,遍历的时候用该叶子结点对应的行,即map[i][j](不是map[j][i]),是因为叶子结点只有一条边,所以找到了
map[i][j]=1的 j 就找到了它的父结点,就可以退出了。
另外,当遍历父结点连接的其他边时也要分情况,若另一端结点不为叶结点,不能直接degree[k]=0;sum++; 而要degree[k]--; 因为度数不为0之后还要遍历。
注:
这个代码没有考虑只有一个根节点的情况,考虑的话也比较简单,加上一个判断n是否为1,若n=1直接输出1并continue进入下一轮即可。
但是不用考虑孤立顶点(完全没有边或有的点没有边)的情况和形成环的情况,因为这是一棵树,除了上述根节点孤立外不可能有额外孤立的点,也不可能成环。