30.小游戏(贪心)

description

谷同学很喜欢玩计算机游戏,特别是战略游戏,但是有时他不能尽快找到解所以常常感到很沮丧。现在面临如下问题:他必须在一个中世纪的城堡里设防,城堡里的道路形成一棵无向树。要在结点上安排最少的士兵使得他们可以看到所有边。你能帮助他吗?
你的任务是给出士兵的最少数目。
输入包含多组数据。每组数据表示一棵树,在每组数据中:
第一行是结点的数目。
接下来的几行,每行按如下格式描述一个结点:
结点标识符 : ( 道路的数目 ) 结点标识符1  结点标识符2  ......  结点标识符道路的数目
或者
结点标识符 : (0)
对于 n (0<n<=1500) 个结点,结点标识符是一个从 0 到 n - 1 的整数。每条边在测试用例中只出现一次。
对于每组数据,各给出一个整数表示士兵的最少数目.
 测试输入 期待的输出 时间限制 内存限制 额外进程
测试用例 1以文本方式显示
  1. 4↵
  2. 0:(1) 1↵
  3. 1:(2) 2 3↵
  4. 2:(0)↵
  5. 3:(0)↵
  6. 5↵
  7. 3:(3) 1 4 2↵
  8. 1:(1) 0↵
  9. 2:(0)↵
  10. 0:(0)↵
  11. 4:(0)↵
以文本方式显示
  1. 1↵
  2. 2↵
1秒64M0

code

  1. 注意两种情况

     <1> 有孤立点
     
     <2> 形成了环
    
  2. 题目说没有重复边,但这个代码无视重复边,当然,重复边确实重复存进了二维数组里面

  3. 参考来源: https://www.freesion.com/article/4718435692/,这个贴主应该是我的校友,但是我用的他的代码只能过掉一个测试用例,有孤立点和形成环就不能处理,而且一直在循环,达不到循环退出条件在这里插入图片描述

  4. 不过借鉴的是他的思路,邻接表也是用一个二维数组存储的(虽然邻接表提出就是为了节约内存,不过为了方便吗,也就不管了,直接声明一个nxn的数组~~,空间浪费也无所谓

  5. 思路:
    优先删除全部孤立点,随后考虑度为1的结点(即树中的叶子结点),
    因为叶子结点必须要被覆盖,将其与父亲结点比较可知,将士兵放置在父亲结点可得到更优解。
    删除父亲结点和叶结点,继续寻找新的叶子结点,直到所有结点都被覆盖。
    如果剩下的是环,那么就没有度为1的点了(死循环),这时候找一个度最大的点(贪心算法思想)
    ,放一个soldier,然后死循环又开始运行了~

  6. 草图:(真的是草图)

  7. 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

  1. scanf()的返回值是正确读取的数量,如果一开始就遇到换行符,会自动跳过,不管有几个换行符都会跳过
  2. i,j,k,h,t设置的太多,其中输错了一个,导致我程序莫名语法错误(访问了不存在的下标),调试了好几遍才找到 o(︶︿︶)o
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
跳跃游戏是一个贪心算法问题。在这个问题中,我们需要判断是否能够从数组的第0个位置跳跃到数组的最后一个位置。 我们可以使用贪心算法决这个问题。我们从第0个位置开始,一直跳到最后一个位置,每次选择能够跳跃最远的位置作为下一个跳跃点。我们使用一个变量max_index来记录当前能够跳到的最远位置。 具体步骤如下: 1. 创建一个空数组index,用于存储每个位置能够跳到的最远位置。 2. 遍历给定的数组nums,计算每个位置能够跳到的最远位置,并将其存入index数组。 3. 初始化变量jump为0,表示当前所在的位置。 4. 初始化变量max_index为index,表示当前能够跳到的最远位置。 5. 使用while循环,当jump小于index数组的大小且jump小于等于max_index时,执行循环体。 6. 在循环体中,如果max_index小于index[jump],则更新max_index为index[jump],表示当前能够跳得更远。 7. 每次循环结束后,将jump自增1。 8. 在循环结束后,判断jump是否等于index数组的大小,如果等于,则表示能够跳到最后一个位置,返回true,否则返回false。 代码如下所示: ```cpp bool CanJump(std::vector<int>& nums) { std::vector<int> index; for (unsigned int i = 0; i < nums.size(); i++) { index.push_back(i + nums[i]); } unsigned int jump = 0; int max_index = index[0]; while (jump < index.size() && jump <= max_index) { if (max_index < index[jump]) { max_index = index[jump]; } jump++; } if (jump == index.size()) { return true; } return false; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值