题目描述
作物杂交是作物栽培中重要的一步。已知有 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的从小到大排序。