作物杂交

作物杂交

作物杂交是作物栽培中重要的一步。

已知有 N 种作物 (编号 1N),第 i 种作物从播种到成熟的时间为 Ti

作物之间两两可以进行杂交,杂交时间取两种中时间较长的一方。

如作物 A 种植时间为 5 天,作物 B 种植时间为 7 天,则 AB 杂交花费的时间为 7 天。

作物杂交会产生固定的作物,新产生的作物仍然属于 N 种作物中的一种。

初始时,拥有其中 M 种作物的种子 (数量无限,可以支持多次杂交)。

同时可以进行多个杂交过程。

求问对于给定的目标种子,最少需要多少天能够得到。

如存在 4 种作物 ABCD,各自的成熟时间为 5 天、7 天、3 天、8 天。

初始拥有 AB 两种作物的种子,目标种子为 D,已知杂交情况为 A×BCA×CD

则最短的杂交过程为:

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

8 天到第 12 天 (作物 A 的时间),A×CD

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

输入格式

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

2 行包含 N 个整数,其中第 i 个整数表示第 i 种作物的种植时间 Ti

3 行包含 M 个整数,分别表示已拥有的种子类型 KjKj 两两不同。

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

输出格式

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

样例解释

1N2000,
2MN,
1K105,
1TN,
1Ti100,
1KjM,

保证目标种子一定可以通过杂交得到。
不保证作物 AB 杂交只能生成作物 C(也就是说,A×BCA×BD 可能同时在输入中出现)
不保证作物 C 只能由作物 AB 杂交生成(也就是说,A×BDA×CD 可能同时在输入中出现)。
不保证同一杂交公式不在输入中重复出现。

输入样例:

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 天。

 

解题思路

  好题,通过这道题可以知道 Bellman-Ford 算法是如何通过动态规划得到的。同时学习到与平时不同的建图方式。

  首先 Bellman-Ford 算法本质就是个dp,定义状态f(i,j)表示从源点出发所有在i步以内(经过不超过i+1个点,即经过不超过i条边)可以到达点j的路径的集合,属性就是路径长度的最小值。根据节点j的所有出边(可以到达的点)来进行状态转移。状态转移方程如下图:

  最后从源点经过不超过n1步到点x的最短路径就是f(n1,x)。为什么是在n1步内?这是因为如果从源点经过超过n1步到达点x,意味着在这条路径中存在至少n+1个点,又因为最多只有n个不同的点,因此根据抽屉原理路径中必然至少存在两个相同的点,这意味着这路径中存在环。如果这个环是负环,那么可以一直走这个环,从源点到点x的最短路径就是负无穷了,即不存在最短路。否则如果是正环,那么我们可以把这个环去掉,路径长度会变小,就矛盾了。

  Bellman-Ford 算法可以把第一维去掉,就变成我们经常用的dist[j]了,表示从源点出发到点j的最短路径。Bellman-Ford 算法的时间复杂度为O(nm),其中n是点的数量,m是边的数量。

  在本题中同样可以借用 Bellman-Ford 算法的思路来定义状态。定义状态f(i,j)表示所有在i步以内可以生成作物j的方法的集合,属性就是所需时间的最小值。状态转移方程和上面的很类似,假设有x×yj,那么状态转移方程就是f(i,j)=max{f(i1,x), f(i1,y)}+max{wx, wy}

  这个是从其他状态转移到f(i,j),那么如何从f(i,j)转移到其他状态呢?这就涉及到如何建图了。

  建图的方式很巧妙,每条边除了存权值外还可以存其他的东西。假设有关系A×BC,那么我们建两条边,一条是AC,边的信息还要存一个B,表示AB生成C。另外一条是BC,边的信息还要存一个A,表示BA生成C。可以理解为每次枚举到j=A,我们需要把所有与A配对的点取出来更新可以生成的作物。这样状态转移方程就可以写成f(i+1,uk)=min{f(i+1,uk), max{f(i,j), f(i,pk)}+max{wj, wpk}}

  其中有j×pkuk

  在这题如果直接暴力循环n1的话时间复杂度为O(n×m),会超时。可以加个优化,如果对于某个i发现所有的状态都没用被更新过那么就可以退出了。同时还要把第一维去掉,否则还是会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 就可以了。

  建图的方式与上面的一样,定义dist[i]表示从源点到点i的最短距离,发现状态转移是存在环的,因此问题就变成了在有环图求最短路的问题,由于边权非负所以可以用跑 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/

posted @   onlyblues  阅读(174)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-03-08 组合数递推公式的推导
Web Analytics
点击右上角即可分享
微信分享提示