【蓝桥杯】作物杂交

题目描述

作物杂交是作物栽培中重要的一步。已知有 N 种作物 (编号 1 至 N ),第 i 种作物从播种到成熟的时间为 Ti​。作物之间两两可以进行杂交,杂交时间取两种中时间较长的一方。如作物 A 种植时间为 5 天,作物 B 种植时间为 7 天,则 AB 杂交花费的时间为 7 天。作物杂交会产生固定的作物,新产生的作物仍然属于N 种作物中的一种。

初始时,拥有其中 M 种作物的种子 (数量无限,可以支持多次杂交)。同时可以进行多个杂交过程。求问对于给定的目标种子,最少需要多少天能够得到。

如存在 4 种作物 ABCD,各自的成熟时间为 5 天、7 天、3 天、8 天。初始拥有 AB 两种作物的种子,目标种子为 D,已知杂交情况为 A × B → C,A × C → D。则最短的杂交过程为:

第 1 天到第 7 天 (作物 B 的时间),A × B → C。

第 8 天到第 12 天 (作物 A 的时间),A × C → D。

花费 12 天得到作物 D 的种子。

输入描述

输入的第 1 行包含 4 个整数 N,M,K,T,N 表示作物种类总数 (编号 1 至 N),M 表示初始拥有的作物种子类型数量,K 表示可以杂交的方案数,T 表示目标种子的编号。

第 2 行包含 N 个整数,其中第 i 个整数表示第 i 种作物的种植时间 Ti​ (1≤Ti​≤100)。

第 3 行包含 M 个整数,分别表示已拥有的种子类型 Kj​ (1≤Kj​≤M),Kj​ 两两不同。

第 4 至 K + 3 行,每行包含 3 个整数A,B,C,表示第 A 类作物和第B 类作物杂交可以获得第 C 类作物的种子。

其中,1≤N≤2000,2≤M≤N,1≤K≤105,1≤T≤N, 保证目标种子一定可以通过杂交得到。

输出描述

输出一个整数,表示得到目标种子的最短杂交时间。

输入输出样例

示例

输入

6 2 4 6
5 3 4 6 4 9
1 2
1 2 3
1 3 4
2 3 5
4 5 6

 输出

16

 样例说明

第 1 天至第 5 天,将编号 1 与编号 2 的作物杂交,得到编号 3 的作物种子。

第 6 天至第 10 天,将编号 1 与编号 3 的作物杂交,得到编号 4 的作物种子。

第 6 天至第 9 天,将编号 2 与编号 3 的作物杂交,得到编号 5 的作物种子。

第 11 天至第 16 天,将编号 4 与编号 5 的作物杂交,得到编号 6 的作物种子。

总共花费 16 天。

 运行限制

求解代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2010;
typedef pair<int, int> PII;
int w[N], f[N];
bool have[N];

vector<PII> fa[N];

int dfs(int t)
{
    for(int i = 0; i < fa[t].size(); i++)
    {
        int a = fa[t][i].first, b = fa[t][i].second;
        if(!have[a]) dfs(a);
        if(!have[b]) dfs(b);
        if(have[a] && have[b]) 
        {
            have[t] = true;
            f[t] = min(f[t], max(w[a], w[b]) + max(f[a], f[b]));
        }
    }
    return f[t];
}
int main()
{
    int n, m, k, t;
    cin >> n >> m >> k >> t;
    memset(f, 0x3f3f, sizeof f);
    for(int i = 1; i <= n; i++) cin >> w[i];
    for(int i = 1; i <= m; i++)
    {
        int temp;
        cin >> temp;
        have[temp] = true;
        f[temp] = 0;
    }
    for(int i = 1; i <= k; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        fa[c].push_back({a, b}); 
    }
    cout << dfs(t);
    return 0;
}

学习过程(学习大佬的代码)

求解时,要根据目标种子一个一个往前追溯,即比如需要6号种子需要4号和5号,但是种子库没有这两种,于是追溯4号和5号。

以4号种子为例(我们先计算4号种子),重复刚刚的步骤,发现4号需要1号和3号,但是发现种子库有1号没有3号,于是继续追溯3号。

继续重复刚刚的步骤,发现3号需要的1号和2号种子种子库中都有,于是计算此时目标种子花费的时间,并且返回该值,该值为5。计算过程是5=0+5(0为当前花费的时间,5为这一步递归中获取目标种子需要花费的时间)(注意这里是获取,而数量是无限的。与作物的种植时间不一样的

比如获取3号种子需要5天,于是这一步返回值为5,然后回到目标种子为4的递归中。

获取4号种子需要1号和3号种子,而1号种子需要5天,3号种子需要4天,所以获取4号种子需要的时间为5天(二者最大值),因此这一步的返回值为10。计算过程为10=5+5(5位已花费的时间,另外一个5为这一步获取4号种子需要的时间),因此获取4号的总时间是10天。此时为了获取4号种子已花费时间总计为10天,返回值为10。

在此基础上,我们继续追溯5号种子也是同样的道理,重复刚刚的步骤,发现获取5号种子需要2号种子和3号种子,而此时我们通过对4号的追溯,种子库中已经有1、2、3、4号种子,所以可以直接合成5号种子(不要重复计算)

2号种子需要3天,3号种子需要4天,故获取5号种子需要4天的时间,返回值为14。计算过程为14=10+4。

因此此时我们拥有了1、2、3、4、5号种子,已经花费了14天的时间,然后我们直接合成6号种子,发现4号种子需要6天,5号种子需要4天,因此合成6号种子需要20天。计算过程是10=14+6。

最后发现,答案错误!正确答案应该是16而不是20。那么哪一步出问题了呢?

经过仔细推敲,我发现,在3号种子合成之后,获取4号和5号种子的过程中,其实是可以同时进行的,因为4号需要1号和3号,5号需要2号和3号,而此时种子库中1、2、3都有。所以,这个就是一步一步算的弊端。(因为有先后顺序的弊端,无法同时进行种植)

在这个求解代码中,大佬用了一个函数f[N]来保存了获取每一种种子需要的时间,比如种子库已有的1和2号种子赋值为0(即已有,不需要另外获取),同时其他的f[N]中的其他值通过

memset(f, 0x3f3f, sizeof f);

都初始化为int类型的较大整数,注意不是0哦。

这样方便了后续代码中

f[t] = min(f[t], max(w[a], w[b]) + max(f[a], f[b]));

max是子种子中最大花费的时间(题意),min是比较f[t]当前状态与合成t号种子花费的时间。

如果f[t]依旧是初始化时较大的整数时max(w[a], w[b]赋值给他代表合成这个种子花费的时间。

如果是0则代表,种子库中已有该种种子,避免了重复计算。

如果既不是0也不是较大的整数,那么代表的意思是在之前的追溯中,have(t)为true,该种子已有,与其他合成方法(如果有)的时间长短,这样可以避免不是最短时间的情况。

不得不说,这个写出这个代码的人真的很聪明!!!

最后,这样经过多种追溯后,返回的f[t]即为最短杂交时间

太牛了,天才代码。

这一步的代码的作用主要是用max(f[a],f[b])可以避免之前那种情况,比如正确来说获取5号种子需要9天,获取4号种子需要10天,而这两个天数不需要一步步来,可以同时进行,所以取其中的最大值。

附上新学到的函数用法

memset函数

memset函数用于将一段内存区域初始化为指定的值,其头文件为string.h。其函数原型为:

void *memset(void *s, int c, size_t n);

函数说明:

  • s:指向要填充的内存块的指针;
  • c:要被填充的值,int型,但会自动转换为unsigned char类型;
  • n:要被设置为该值的连续字节数。

函数返回一个指向s的指针。

例如,下面的语句将数组a中的前10个字节都初始化为0:

int a[20];
memset(a, 0, 10*sizeof(int));

需要注意的是,memset函数通常只适用于填充连续的、基本类型的内存区域。对于结构体或者其他类型的变量,初始化通常是通过赋初值或者构造函数来实现的。此外,在使用memset函数时,应当保证指定的字节数大于等于0,并且不大于目标变量的大小。

typedef pair<int, int> PII

它的作用是将pair<int, int>这种数据类型重新定义为PII。其中pair<int, int>表示包含两个int类型元素的二元组,而PII则是这种二元组类型的一个新名字。

该代码定义了一个名为PII的新类型,是一个“pair”类型,其中包含两个int类型的元素。用这个新类型可以方便地表示一对int类型的值,用于存储各种数据结构中的键值对等数据。PII类型通常用作各种算法的返回类型等。

例如,以下代码将一个名为“mp”的map(关联数组)的所有键值对按照第一个元素(int类型)从小到大排序:

map<int, int> mp;
// ... some operations to insert key-value pairs into the map ...
vector<PII> v;
for (auto& p : mp) {
    v.push_back(p);
}
sort(v.begin(), v.end());

在这里,vector<PII>类型的变量“v”存储了pair类型“p”中的int型键和值,在调用sort函数时,根据pair的第一个元素进行排序,以实现对map的从小到大排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会C++的Mr.Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值