作物杂交
作物杂交
作物杂交是作物栽培中重要的一步。
已知有 种作物 (编号 至 ),第 种作物从播种到成熟的时间为 。
作物之间两两可以进行杂交,杂交时间取两种中时间较长的一方。
如作物 种植时间为 天,作物 种植时间为 天,则 杂交花费的时间为 天。
作物杂交会产生固定的作物,新产生的作物仍然属于 种作物中的一种。
初始时,拥有其中 种作物的种子 (数量无限,可以支持多次杂交)。
同时可以进行多个杂交过程。
求问对于给定的目标种子,最少需要多少天能够得到。
如存在 种作物 ,各自的成熟时间为 天、 天、 天、 天。
初始拥有 两种作物的种子,目标种子为 ,已知杂交情况为 ,。
则最短的杂交过程为:
第 天到第 天 (作物 的时间),。
第 天到第 天 (作物 的时间),。
花费 天得到作物 的种子。
输入格式
输入的第 行包含 个整数 , 表示作物种类总数 (编号 至 ), 表示初始拥有的作物种子类型数量, 表示可以杂交的方案数, 表示目标种子的编号。
第 行包含 个整数,其中第 i 个整数表示第 种作物的种植时间 。
第 行包含 个整数,分别表示已拥有的种子类型 , 两两不同。
第 至 行,每行包含 个整数 ,表示第 类作物和第 类作物杂交可以获得第 类作物的种子。
输出格式
输出一个整数,表示得到目标种子的最短杂交时间。
样例解释
,
,
,
,
,
,
保证目标种子一定可以通过杂交得到。
不保证作物 和 杂交只能生成作物 (也就是说, 和 可能同时在输入中出现)
不保证作物 只能由作物 和 杂交生成(也就是说, 和 可能同时在输入中出现)。
不保证同一杂交公式不在输入中重复出现。
输入样例:
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
样例解释
第 天至第 天,将编号 与编号 的作物杂交,得到编号 的作物种子。
第 天至第 天,将编号 与编号 的作物杂交,得到编号 的作物种子。
第 天至第 天,将编号 与编号 的作物杂交,得到编号 的作物种子。
第 天至第 天,将编号 与编号 的作物杂交,得到编号 的作物种子。
总共花费 天。
解题思路
好题,通过这道题可以知道 Bellman-Ford 算法是如何通过动态规划得到的。同时学习到与平时不同的建图方式。
首先 Bellman-Ford 算法本质就是个dp,定义状态表示从源点出发所有在步以内(经过不超过个点,即经过不超过条边)可以到达点的路径的集合,属性就是路径长度的最小值。根据节点的所有出边(可以到达的点)来进行状态转移。状态转移方程如下图:
最后从源点经过不超过步到点的最短路径就是。为什么是在步内?这是因为如果从源点经过超过步到达点,意味着在这条路径中存在至少个点,又因为最多只有个不同的点,因此根据抽屉原理路径中必然至少存在两个相同的点,这意味着这路径中存在环。如果这个环是负环,那么可以一直走这个环,从源点到点的最短路径就是负无穷了,即不存在最短路。否则如果是正环,那么我们可以把这个环去掉,路径长度会变小,就矛盾了。
Bellman-Ford 算法可以把第一维去掉,就变成我们经常用的了,表示从源点出发到点的最短路径。Bellman-Ford 算法的时间复杂度为,其中是点的数量,是边的数量。
在本题中同样可以借用 Bellman-Ford 算法的思路来定义状态。定义状态表示所有在步以内可以生成作物的方法的集合,属性就是所需时间的最小值。状态转移方程和上面的很类似,假设有,那么状态转移方程就是
这个是从其他状态转移到,那么如何从转移到其他状态呢?这就涉及到如何建图了。
建图的方式很巧妙,每条边除了存权值外还可以存其他的东西。假设有关系,那么我们建两条边,一条是,边的信息还要存一个,表示和生成。另外一条是,边的信息还要存一个,表示和生成。可以理解为每次枚举到,我们需要把所有与配对的点取出来更新可以生成的作物。这样状态转移方程就可以写成
其中有。
在这题如果直接暴力循环的话时间复杂度为,会超时。可以加个优化,如果对于某个发现所有的状态都没用被更新过那么就可以退出了。同时还要把第一维去掉,否则还是会TLE。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2010, M = 2e5 + 10; 5 6 int w[N]; 7 int head[N], e[M], p[M], ne[M], idx; 8 int f[N]; 9 10 void add(int v, int w, int x) { 11 e[idx] = w, p[idx] = x, ne[idx] = head[v], head[v] = idx++; 12 } 13 14 int main() { 15 int n, m, k, t; 16 scanf("%d %d %d %d", &n, &m, &k, &t); 17 for (int i = 1; i <= n; i++) { 18 scanf("%d", w + i); 19 } 20 memset(f, 0x3f, sizeof(f)); 21 while (m--) { 22 int x; 23 scanf("%d", &x); 24 f[x] = 0; 25 } 26 memset(head, -1, sizeof(head)); 27 while (k--) { 28 int v, w, x; 29 scanf("%d %d %d", &v, &w, &x); 30 add(v, x, w), add(w, x, v); 31 } 32 for (int i = 0; i < n - 1; i++) { 33 bool flag = false; 34 for (int j = 1; j <= n; j++) { 35 for (int k = head[j]; k != -1; k = ne[k]) { 36 int t = max(f[j], f[p[k]]) + max(w[j], w[p[k]]); 37 if (f[e[k]] > t) { 38 f[e[k]] = t; 39 flag = true; 40 } 41 } 42 } 43 if (!flag) break; // 没有状态被更新过 44 } 45 printf("%d", f[t]); 46 47 return 0; 48 }
还可以写 SPFA 来优化 Bellman-Ford 算法,AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2010, M = 2e5 + 10; 5 6 int w[N]; 7 int head[N], e[M], p[M], ne[M], idx; 8 int dist[N]; 9 bool vis[N]; 10 11 void add(int v, int w, int x) { 12 e[idx] = w, p[idx] = x, ne[idx] = head[v], head[v] = idx++; 13 } 14 15 int main() { 16 int n, m, k, t; 17 scanf("%d %d %d %d", &n, &m, &k, &t); 18 for (int i = 1; i <= n; i++) { 19 scanf("%d", w + i); 20 } 21 queue<int> q; 22 memset(dist, 0x3f, sizeof(dist)); 23 while (m--) { 24 int x; 25 scanf("%d", &x); 26 q.push(x); 27 dist[x] = 0; 28 vis[x] = true; 29 } 30 memset(head, -1, sizeof(head)); 31 while (k--) { 32 int v, w, x; 33 scanf("%d %d %d", &v, &w, &x); 34 add(v, x, w), add(w, x, v); 35 } 36 while (!q.empty()) { 37 int t = q.front(); 38 q.pop(); 39 vis[t] = false; 40 for (int i = head[t]; i != -1; i = ne[i]) { 41 int d = max(dist[t], dist[p[i]]) + max(w[t], w[p[i]]); 42 if (dist[e[i]] > d) { 43 dist[e[i]] = d; 44 if (!vis[e[i]]) vis[e[i]] = true, q.push(e[i]); 45 } 46 } 47 } 48 printf("%d", dist[t]); 49 50 return 0; 51 }
这题还可以用 Dijkstra 来写。这是因为求最短路,并且所有的边权均是非负数。思想一样是从动态规划出发,然后发现是个有环图,所以跑个 Dijkstra 就可以了。
建图的方式与上面的一样,定义表示从源点到点的最短距离,发现状态转移是存在环的,因此问题就变成了在有环图求最短路的问题,由于边权非负所以可以用跑 Dijkstra。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef pair<int, int> PII; 5 6 const int N = 2010, M = 2e5 + 10; 7 8 int w[N]; 9 int head[N], e[M], p[M], ne[M], idx; 10 int dist[N]; 11 bool vis[N]; 12 13 void add(int v, int w, int x) { 14 e[idx] = w, p[idx] = x, ne[idx] = head[v], head[v] = idx++; 15 } 16 17 int main() { 18 int n, m, k, t; 19 scanf("%d %d %d %d", &n, &m, &k, &t); 20 for (int i = 1; i <= n; i++) { 21 scanf("%d", w + i); 22 } 23 priority_queue<PII, vector<PII>, greater<PII>> pq; 24 memset(dist, 0x3f, sizeof(dist)); 25 while (m--) { 26 int x; 27 scanf("%d", &x); 28 pq.push({0, x}); 29 dist[x] = 0; 30 } 31 memset(head, -1, sizeof(head)); 32 while (k--) { 33 int v, w, x; 34 scanf("%d %d %d", &v, &w, &x); 35 add(v, x, w), add(w, x, v); 36 } 37 while (!pq.empty()) { 38 int t = pq.top().second; 39 pq.pop(); 40 if (vis[t]) continue; 41 vis[t] = true; 42 for (int i = head[t]; i != -1; i = ne[i]) { 43 int d = max(dist[t], dist[p[i]]) + max(w[t], w[p[i]]); 44 if (dist[e[i]] > d) { 45 dist[e[i]] = d; 46 pq.push({dist[e[i]], e[i]}); 47 } 48 } 49 } 50 printf("%d", dist[t]); 51 52 return 0; 53 }
参考资料
AcWing 3305. 作物杂交(蓝桥杯集训·每日一题):https://www.acwing.com/video/4647/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17191565.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-03-08 组合数递推公式的推导