description
谷同学很喜欢玩计算机游戏,特别是战略游戏,但是有时他不能尽快找到解所以常常感到很沮丧。现在面临如下问题:他必须在一个中世纪的城堡里设防,城堡里的道路形成一棵无向树。要在结点上安排最少的士兵使得他们可以看到所有边。你能帮助他吗?
你的任务是给出士兵的最少数目。
输入包含多组数据。每组数据表示一棵树,在每组数据中:
第一行是结点的数目。
接下来的几行,每行按如下格式描述一个结点:
结点标识符 : ( 道路的数目 ) 结点标识符1 结点标识符2 ...... 结点标识符道路的数目
或者
结点标识符 : (0)
对于 n (0<n<=1500) 个结点,结点标识符是一个从 0 到 n - 1 的整数。每条边在测试用例中只出现一次。
对于每组数据,各给出一个整数表示士兵的最少数目.
code
-
注意两种情况
<1> 有孤立点 <2> 形成了环
-
题目说没有重复边,但这个代码无视重复边,当然,重复边确实重复存进了二维数组里面
-
参考来源: https://www.freesion.com/article/4718435692/,这个贴主应该是我的校友,但是我用的他的代码只能过掉一个测试用例,有孤立点和形成环就不能处理,而且一直在循环,达不到循环退出条件
-
不过借鉴的是他的思路,邻接表也是用一个二维数组存储的(虽然邻接表提出就是为了节约内存,不过为了方便吗,也就不管了,直接声明一个nxn的数组~~,空间浪费也无所谓
-
思路:
优先删除全部孤立点,随后考虑度为1的结点(即树中的叶子结点),
因为叶子结点必须要被覆盖,将其与父亲结点比较可知,将士兵放置在父亲结点可得到更优解。
删除父亲结点和叶结点,继续寻找新的叶子结点,直到所有结点都被覆盖。
如果剩下的是环,那么就没有度为1的点了(死循环),这时候找一个度最大的点(贪心算法思想)
,放一个soldier,然后死循环又开始运行了~ -
草图:(真的是草图)
-
cpp代码
/*
* @Author: 鱼香肉丝没有鱼
* @Date: 2021-11-13 07:29:25
* @Last Modified by: 鱼香肉丝没有鱼
* @Last Modified time: 2021-11-13 07:29:25
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 1503
//每行第0个元素代表这个节点输入完后连接了多少个节点,即从第1个元素开始就是这个节点连接的其他节点
int adj[N][N]; //邻接表, adjacency list
int degree[N]; //每一个顶点的度
int flag;
//顶点数
int n;
//建立邻接图
void CreateGraph()
{
int vertex; //顶点
int point;
int i, j;
int num;
memset(adj, -1, sizeof(adj)); //-1表示没有连接
memset(degree, 0, sizeof(degree));
for(int i = 0; i < n; i++)
adj[i][0] = 0; //初始邻接数量都为0
//输入
for(i = 0; i < n; i++) {
scanf("%d:(%d)", &vertex, &num);
if(num != 0) { //如果输入的num有非0的,即图中有边,flag=1
flag = 1;
}
for(j = 0; j < num; j++) {
scanf("%d", &point); //输入他指向的顶点
adj[vertex][0]++; // point点连接的节点个数加1
adj[vertex][adj[vertex][0]] = point; // point点后面依次添加p点
adj[point][0]++; // p点连接的节点个数加1
adj[point][adj[point][0]] = vertex; // p点后面依次添加point点
degree[point]++; // p点度数加1
degree[vertex]++; // point点度数加1
}
}
}
void LeafSolve(int j, int* count, int* m)
{
int i, t, h;
int k = -2; //如果遍历之后还是-2,说明出错了,没有找到正确的k值
degree[j]--; //该点度数减1
//度数为1的节点只连接着1个节点,所以该行取下标为1的元素就是该度数为1的点的父节点k
//刚开始是这样,但是后面经过处理之后k就不能直接取第一个值了
// k = adj[j][1];
for(i = 1; i <= adj[j][0]; i++)
if(adj[j][i] != -1) {
k = adj[j][i];
adj[j][i] = -1; // clear j到k的指向
break;
}
if(k == -2)
return;
adj[j][0] = 0;
(*m)--; //这个点也可以扔掉了
(*count)++; //在这个父节点上设置一个士兵
//接下来要删除k的所有子节点
for(i = 1; i <= adj[k][0]; i++) {
if(adj[k][i] == j) { // j到k的指向已经清除了,现在清除k到j的指向
adj[k][i] = -1; //在k连接的点中找到j,并将这个j的位置设置为-1,k和j就彻底断绝了关系
degree[k]--;
}
if(adj[k][i] == -1)
continue; //如果当前位置的子节点已经删除过就不进行后面的操作
t = adj[k][i]; //获得当前这个k的子节点的编号
if(degree[t] == 1) //如果这个子节点是1度的
{
degree[t] = 0;
(*m)--; //图中少了一个点
adj[k][i] = -1; //清除k到t的指向
adj[t][1] = -1; //清除t到k的指向
degree[k]--;
adj[t][0] = 0;
}
else //如果这个子节点不是1度的
{
degree[t]--;
adj[k][i] = -1; //清除k到t的指向
degree[k]--;
for(h = 1; h <= adj[t][0]; h++) { //在t连接的节点中找到k,将k的位置设为-1
if(adj[t][h] == k) {
adj[t][h] = -1; //清除t到k的指向
break;
}
}
}
}
if(degree[k] == 0) {
(*m)--; //图中又少了个点
adj[k][0] = 0;
}
}
//解决环的情况
void CycleSolve(int* m, int* count)
{
int i, j, k, t;
for(i = 0; i < n; i++) {
if(degree[i] != 0)
k = i;
}
for(j = i; j < n; j++) {
if(degree[i] < degree[j])
k = j;
}
(*count)++;
//顶点k放了一个士兵,删除和他链接的点
for(i = 1; i <= adj[k][0]; i++) {
t = adj[k][i]; // k指向的顶点
adj[k][i] = -1; //清楚k指向t
degree[k]--;
for(j = 1; j <= adj[t][0]; j++) {
if(adj[t][j] == k) {
adj[t][j] = -1;
degree[t]--;
break;
}
}
}
if(degree[k] == 0) {
(*m)--;
adj[k][0] = 0;
}
}
int main()
{
// freopen("file in.txt", "r", stdin);
int i, j;
int cycleflag;
int count, m; // count:士兵数量
while(scanf("%d", &n) == 1) { // scanf 的返回值是正确读取的数量
flag = 0;
if(n == 1) { //如果只有一个点,答案为1
printf("1\n");
continue;
}
CreateGraph();
if(flag == 0) { //如果图中没边
printf("0\n");
continue;
}
else {
count = 0; // soldier 数量
m = n; // m变量用来覆盖所有点
for(i = 0; i < n; i++) //如果放在循环内部会造成重复计算
if(degree[i] == 0)
m--; // delete the isolate vertex,孤立的顶点没有边,不需要安排士兵
while(m > 0) {
cycleflag = 1;
for(j = 0; j < n; j++) { //遍历所有的点
if(degree[j] == 1) //寻找度数为1的点,进行操作
{
cycleflag = 0; //只要有度为1的点,就不是环的情况
LeafSolve(j, &count, &m);
}
}
//迭代处理完了原本存在的孤立点和只有一个度的点,现在考虑环的情况了
if(cycleflag) {
// 找出度最大的顶点k,在那里放一个士兵
CycleSolve(&m, &count);
}
}
printf("%d\n", count);
}
}
return 0;
}
summary
- scanf()的返回值是正确读取的数量,如果一开始就遇到换行符,会自动跳过,不管有几个换行符都会跳过
- i,j,k,h,t设置的太多,其中输错了一个,导致我程序莫名语法错误(访问了不存在的下标),调试了好几遍才找到 o(︶︿︶)o