P1460 健康的荷斯坦奶牛 Healthy Holsteins(洛谷)

题目描述

农民 John 以拥有世界上最健康的奶牛为傲。他知道每种饲料中所包含的牛所需的最低的维他命量是多少。请你帮助农夫喂养他的牛,以保持它们的健康,使喂给牛的饲料的种数最少。

给出牛所需的最低的维他命量,输出喂给牛需要哪些种类的饲料,且所需的饲料剂量最少。

维他命量以整数表示,每种饲料最多只能对牛使用一次,数据保证存在解。

输入格式

第一行一个整数 vvv,表示需要的维他命的种类数。
第二行 vvv 个整数,表示牛每天需要的每种维他命的最小量。

第三行一个整数 ggg,表示可用来喂牛的饲料的种数。
下面 ggg 行,第 nnn 行表示编号为 nnn 饲料包含的各种维他命的量的多少。

输出格式

输出文件只有一行,包括牛必需的最小的饲料种数 ppp;后面有 ppp 个数,表示所选择的饲料编号(按从小到大排列)。

如果有多个解,输出饲料序号最小的(即字典序最小)。

输入输出样例

输入 #1
4
100 200 300 400
3
50  50  50  50
200 300 200 300
900 150 389 399
输出 #1
2 1 3

说明/提示

【数据范围】
对于 100%100\%100% 的数据,1≤v≤251\le v \le 251v25,1≤g≤151\le g \le 151g15。
输入的所有整数在 [1,1000][1,1000][1,1000] 范围内。

 

代码与题解:

#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;


//解题核心思想:题目的最终目的是求最少的饲料种类数(与维他命剂量没关系),因此可以使用bfs算法进行求解
//本题实际上dfs方式求解更简单(只需要dfs(选当前某一饲料)和dfs(不选当前某一饲料即可))
//但关键点在于:最有解中种类数相同的有好几种情况满足,题目要求输出最小的编号

//思路解法一:每次bfs一个节点 直到找到满足的条件为止  记录饲料种数和饲料编号  因此可以使用for循环(但是可能会出现MLE)
//思路解法二:把所有的饲料(每一种)当做出发结点,首先全部压入队列(编号小的先压入),一起进行bfs,因此只要第一次找到满足条件的则是满足条件且编号最小的解
int m,n;
int mini[26];       //mini[i]代表牛对第i种维他命的最小需求
int si[30][30];     //si[i][j]代表第i种饲料中j维他命的含量
int color[30]={0};        //用于标记哪种饲料被使用过

typedef struct node{
    int v[30];      //储存结点的维他命数量的情况
    int dis[16];    //储存选择的饲料种类(路径)
    int num;          //选择饲料的种类数量
}N;

bool panduan(int *a){     //判断是否满足了最小要求
    for(int i=1;i<=m;i++){
        if(a[i]<mini[i])    return false;       //只要有一种维他命缺少就不满足
    }
    return true;
}

void bfs(){
    queue<N> q;
    for(int i=1;i<=n;i++){          //n种饲料  因此先生成n个初始结点
        N temp;
        for(int j=1;j<=m;j++){      //m种维他
            temp.v[j]=si[i][j];
        }
        temp.dis[0]=i;      //注意路径下标从0开始 从0~temp.num-1
        temp.num=1;
        q.push(temp);
    }

    while(!q.empty()){
        N cur=q.front();
        q.pop();

        if(panduan(cur.v)){         //找到了立即输出
            cout<<cur.num<<" ";
            for(int i=0;i<cur.num;i++){
                cout<<cur.dis[i]<<" ";
            }
            return;
        }

        //若上述没有找到 则执行下面的遍历其他节点
        //节点压入队列的顺序方式为  只压入比刚刚pop出的节点编号大的结点(而且压入的顺序是从小到大  即最开始从pop的结点编号+1开始)
        //每个初始结点都按照这种只入队编号比pop结点编号大的结点的方式,并且节点最开始也是从1~n的,因此不需要担心会有遗漏的情况出现
        //可以理解为:遍历的方向或者入队顺序:
        //第一个初始点出发(指的是饲料编号):1 2 3 ……n
        //第二个初始点出发: 2 3 4…… n 以此类推

        for(int i=cur.dis[cur.num-1]+1;i<=n;i++){       //这样设置可以保证i一定未走过
            N next=cur;
            for(int j=1;j<=m;j++){          //吃了第i种饲料 所以把对应的维生素加上
                next.v[j]+=si[i][j];
            }
            next.dis[next.num]=i;
            next.num++;
            q.push(next);
        }
    }
}


int main(){
    cin>>m;
    for(int i=1;i<=m;i++){
        cin>>mini[i];
    }

    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>si[i][j];
        }
    }
    bfs();
    return 0;
}

 

posted @ 2020-09-23 16:46  neverstopcoding  阅读(488)  评论(0编辑  收藏  举报