题目大意
作物杂交是作物栽培中重要的一步。已知有 NN 种作物 (编号 11 至 NN ),第 ii 种作物从播种到成熟的时间为 t_it
i
。作物之间两两可以进行杂交,杂交时间取两种中时间较长的一方。如作物 A 种植时间为 55 天,作物 B 种植时间为 77 天,则 ABAB 杂交花费的时间为 77 天。作物杂交会产生固定的作物,新产生的作物仍然属于 NN 种作物中的一种。
初始时,拥有其中 MM 种作物的种子 (数量无限,可以支持多次杂交)。同时可以进行多个杂交过程。求问对于给定的目标种子,最少需要多少天能够得到。
如存在 44 种作物 ABCDABCD,各自的成熟时间为 55 天、77 天、33 天、88 天。初始拥有 ABAB 两种作物的种子,目标种子为 DD,已知杂交情况为 A \times B \rightarrow CA×B→C,A \times C \rightarrow DA×C→D。则最短的杂交过程为:
第 11 天到第 7 天 (作物 B 的时间),A \times B \rightarrow CA×B→C。
第 88 天到第 1212 天 (作物 A 的时间),A \times C \rightarrow DA×C→D。
解题思路
若作物 AA、BB 可杂交出作物 ii,则我们称 AA、BB 为 ii 的父亲 ,ii 为 AA、BB 的儿子。显然一种种子可以拥有多个父亲,也可以拥有多个儿子。
我们定义 dp[i]dp[i] 表示得到第 ii 个作物所需的最少天数。根据题意可得:若作物 AA、BB 可杂交出作物 ii,那么
dp[i] = \min(dp[i] , \max(dp[A],dp[B]) + max(t[A] , t[B]))
dp[i]=min(dp[i],max(dp[A],dp[B])+max(t[A],t[B]))
于是我们可以将已经拥有的种子放入队列,然后每次取出的时候,更新它所有儿子的答案(更新前要先判断一下该儿子的另一个父亲是否已经得到了)。
由于每个种子只会入队出队一次,所以每个种子都只会遍历一遍和它有关的杂交方案,那么复杂度就等于总杂交方案数。
这样乍一看好像可以,但有个致命的问题:我们只会用作物 AA 更新一次作物 ii 的答案。
举个例子:
设作物 55 和作物 44 是作物 11 的父亲、dp[5]=10,dp[4] = 0 , t[5] = 10 , t[4]= 0dp[5]=10,dp[4]=0,t[5]=10,t[4]=0。
那么当作物 55 出队时,作物 55 将会更新作物 11 的答案,即 dp[1] = \min(dp[1], \max(dp[5],dp[4] + \max(t[5],t[4])))dp[1]=min(dp[1],max(dp[5],dp[4]+max(t[5],t[4])))。
而后,作物 55 的某个父亲出队时,它将会更新作物 55 的答案,即 dp[5]dp[5]。若更新后 dp[5]dp[5] 的值小于 1010,则我们也应该再用 dp[5]dp[5] 去更新一次 dp[1]dp[1]。不过由于作物 55 已经出队了,它将不会再去更新它的儿子。这样就会导致答案错误。
为了避免这种情况,我们可以使用优先队列,按照 dp[i]dp[i] 的值从小到大出队。若一个作物的 dpdp 值减少了,则需要重新入队。
或者维护一个时间戳数组 vec[],将第 ii 种作物存入 vec[dp[i]] 中。然后从前往后枚举时间。对于某个时刻,将该时刻 vec 数组中的作物放入队列以更新它们的儿子。若一个作物的 dpdp 值减少了,也需要加入新的时间戳数组中。
最后的答案为 dp[dp[目标种子]](注意作物 AA 和作物BB 可能可以杂交出多种其它作物,不过同一杂交方案不会多次输出)。
AC_Code
import java.io.*;
import java.util.*;
class InputReader{
private final static int BUF_SZ = 65536;
BufferedReader in;
StringTokenizer tokenizer;
public InputReader(InputStream in) {
super();
this.in = new BufferedReader(new InputStreamReader(in),BUF_SZ);
tokenizer = new StringTokenizer("");
}
private String next() {
while (!tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(in.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public int nextInt() {
return Integer.parseInt(next());
}
}
public class Main {
private final static int N = 2010;
private static boolean[] ok = new boolean[N];
private static boolean[] vis = new boolean[N];
private static int n , m , K , T , x , y , z;
private static int[] t = new int[N];
private static int[] dp = new int[N];
private static ArrayList<Integer>[] vec = Arrays.initializeWithDefaultArrayListOfIntInstances(N * N);
private static ArrayList<Pair<Integer, Integer>>[] G = Arrays.initializeWithDefaultArrayListOfPairOfIntInstances(N);
private static LinkedList<Integer> que = new LinkedList<Integer>();
private static void bfs() {
int up = 0;
for (int i = 0; i <= up; i++) {
for (int j : vec[i]) {
if (vis[j]) {
continue;
}
vis[j] = true;
que.offer(j);
while (!que.isEmpty()) {
int u = que.peek();
que.poll();
for (Pair<Integer, Integer> k : G[u]) {
int y = k.first;
int z = k.second;
if (!ok[y]) {
continue;
}
int now = Math.max(dp[u], dp[y]) + Math.max(t[u], t[y]);
if (now < dp[z]) {
dp[z] = now;
vec[dp[z]].add(z);
}
ok[z] = true;
up = Math.max(up, now);
}
}
}
}
}
public static void main(String[] args) {
InputReader cin = new InputReader(System.in);
n = cin.nextInt();
m = cin.nextInt();
K = cin.nextInt();
T = cin.nextInt();
java.util.Arrays.fill(dp , 1000000000);
for (int i = 1; i <= n; i++) {
t[i] = cin.nextInt();
}
for (int i = 1; i <= m; i++) {
x = cin.nextInt();
ok[x] = true;
dp[x] = 0;
vec[0].add(x);
}
for (int i = 1; i <= K; i++) {
x = cin.nextInt();
y = cin.nextInt();
z = cin.nextInt();
G[x].add(new Pair<Integer, Integer>(y, z));
G[y].add(new Pair<Integer, Integer>(x, z));
}
bfs();
System.out.println(dp[T]);
}
}
final class Pair<T1, T2> {
public T1 first;
public T2 second;
public Pair() {
first = null;
second = null;
}
public Pair(T1 firstValue, T2 secondValue) {
first = firstValue;
second = secondValue;
}
public Pair(Pair<T1, T2> pairToCopy) {
first = pairToCopy.first;
second = pairToCopy.second;
}
}
final class Arrays {
public static ArrayList<Integer>[] initializeWithDefaultArrayListOfIntInstances(int length) {
ArrayList<Integer>[] array = new ArrayList[length];
for (int i = 0; i < length; i++) {
array[i] = new ArrayList<Integer>();
}
return array;
}
public static java.util.ArrayList<Pair<Integer, Integer>>[] initializeWithDefaultArrayListOfPairOfIntInstances(int length) {
ArrayList[] array = new ArrayList[length];
for (int i = 0; i < length; i++) {
array[i] = new ArrayList<>();
}
return array;
}
}
copy
视频讲解配套源码
package lanqiao;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
public class lanqiao202009
{
static int INF=Integer.MAX_VALUE;
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int N=0,M=0,K=0,T=0;//N种作物,M个初始种子,K种杂交方案,要得到T种子
N=sc.nextInt();
M=sc.nextInt();
K=sc.nextInt();
T=sc.nextInt();
int[] dp=new int[N+1];//得到作物i所需要的最短时间
int[] ctime=new int[N+1];//每个种子的作物种植的时间
Arrays.fill(dp, INF);//开始时间都无穷大
for(int i=1;i<=N;i++)ctime[i]=sc.nextInt();//录入每个种子生长需要的时间
for(int i=1;i<=M;i++)dp[sc.nextInt()]=0;//M个已知作物,不需要杂交得到
ArrayList<bean>[] ways=new ArrayList[N+1];//每个作物都可以有多个杂交途径得到。way[i]存放 i种子所有可以杂交的方案
for(int i=1;i<=N;i++)ways[i]=new ArrayList<bean>();//对象数组要一个一个实例化里面的元素
for(int i=1;i<=K;i++)
{
int fa=sc.nextInt();
int fb=sc.nextInt();
int gc=sc.nextInt();
//一种方案能得到c的记录下来
ways[gc].add(new bean(fa,fb,Math.max(ctime[fa], ctime[fb])));//时间以2个爸爸中长的为准
}
System.out.println(DFS(dp,ways,T));
}
private static int DFS(int[] dp,ArrayList<bean>[] ways,int T)
{
if(dp[T]!=INF)return dp[T];//已经dfs到了作物T的最优杂交时间,直接使用,记忆化搜索
for(int i=0;i<ways[T].size();i++)//穷举作物T的所有已知的杂交方案
{
int famin=DFS(dp,ways,ways[T].get(i).fa);//要知道孩子,必须先知道他2个爸爸的时间
int fbmin=DFS(dp,ways,ways[T].get(i).fb);
//dp状态转移方程:T的最优时间,等于若干种已知方案dp[T],和当前方案 花费的时间和杂交得到fa 和 fb的时间和
dp[T]=Math.min(dp[T], Math.max(famin, fbmin)+ways[T].get(i).time);
}
return dp[T];
}
}
class bean
{
int fa;
int fb;
int time;//a 和 b 需要time杂交得到 c
public bean(int a,int b,int t)
{
fa=a;fb=b;time=t;
}
}