2020智算之道——复赛第二题java版

2020智算之道——复赛第二题java版

此题难度偏易,不过写起来也没有太容易,但是在比赛的时候,脑子发热,只想着以暴制暴,直接dp爆搜,内存也是直接爆炸,搞人心态。现在想起来,还是自己太菜了,用尽全力也就前面两题ac。害!还是太菜了,我tm直接嘤嘤嘤~~!废话不多说。先给各位爷上题:
2020智算之道——复赛第二题java版_第1张图片
2020智算之道——复赛第二题java版_第2张图片
2020智算之道——复赛第二题java版_第3张图片
先给大家分析一下问题,这道题可以简单的理解为在[n+1,n+1]大小的二维数组中的遍历问题,有点类似于图遍历的问题,但是与图遍历不同的是,一般图的遍历问题只能朝上下左右这四个方向走,而此题却有着包括斜上方的3个方向,所以不能将二者等同起来,但是却可以借用这其中的方法。

按题目的意思,我们要从(0,0)的点出发去往(n,n)的点,其中有两种走法,一种是可以横、竖着走,还有一种就是斜着走。前者的花费为w1,后者的花费为w2。我们的目标是使得这其中的总花费最小。首先,无论是爆搜或是其他方法,第一要考虑的便是花费的合理性,如果走两格普通格子的花费要小于走一格魔法格子的花费(也就是2w1nw1)。如果是走两格普通格子的花费要大于走一步魔法格子的花费(也就是2w1>w2),显然是尽可能多的走魔法格子才好。

1.先给大家讲一讲爆搜的写法,其思想就是简单的dp。首先将普通格子和魔法格子在二维数组中表示出来并标明其花费的代价。其次创建一个dp数组用于保存每一格的相对于之前的最优解。接下来我们要做的便是将这个二维数组填起来便行,而dp[n,n]位置的值就是我们要的最优解的值。(注意:这里是假设魔法格子可以走三个方向)首先如果只能横着走,那么只要依次加上前一个格子的花费和前一个dp数组的花费即可。如果只能竖着走,同上,直接加。然后填里面的数字,其规则是如果斜上方如果是魔法格子,其路径就是从魔法格子跳过来的,如果不是就看其余两个方向,选择其中代价小的方案。(看下图)

2020智算之道——复赛第二题java版_第4张图片
其完整代码如下:

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();
		int k=scanner.nextInt();
		int w1=scanner.nextInt();
		int w2=scanner.nextInt();
		int x[][]=new int[n+1][n+1];
		for(int i=0;i<n+1;i++) {
			for(int j=0;j<n+1;j++) {
				x[i][j]=w1;
			}
		}
		for(int i=0;i<k;i++) {
			int x1=scanner.nextInt();
			int y1=scanner.nextInt();
			x[x1][y1]=w2;
		}
		scanner.close();
		int dp[][]=new int[n+1][n+1];
		dp[0][0]=0;
		for (int i=1;i<n+1; i++) {
            dp[i][0]=dp[i-1][0]+x[i-1][0];
        }
        for (int j=1;j<n+1; j++) {
            dp[0][j]=dp[0][j-1]+x[0][j-1];
        }
        for (int i=1;i<n+1;i++) {
            for (int j=1;j<n+1;j++) {
               if(x[i-1][j-1]!=w2) {
            	   if(dp[i][j-1]>dp[i-1][j]) {
            		   dp[i][j]=dp[i-1][j]+x[i-1][j];
            	   }else {
            		   dp[i][j]=dp[i][j-1]+x[i][j-1];
            	   }
               }else {
            	   dp[i][j]=dp[i-1][j-1]+x[i-1][j-1];
               }
            }
        }

		System.out.print(dp[n][n]);
	}
}

此方法用的是爆搜,时间复杂度是O(n^2)就是用空间去换时间,但是由于给的数据过于巨大,很容易导致内存爆炸,所以在数据量大的题目里面不推荐大家使用,因为真的很容易心态爆炸。

2.接下来给大家讲讲另一种做法,就按第一种做法来说,我们已经知道,如果单纯的遍历数组求解,肯定是不行的,所以不能直接遍历数组求解,那么要怎样考虑呢?

首先我们的目的是尽可能的走魔法方块,我们已经知道所有魔法方块的位置了,只要判断我们最多能走几个方块就行了,可以得到以下几个结论:
(1)只能直走或者斜着走
(2)当前位子上面和左边都是不能走的(假设按照数组的位置来看)
(3)走过的前一个魔法方块的位置x,y轴的坐标都比后一个要走的魔法方块的位置坐标小,既要保证魔法方块是绝对递增的。

经此分析,我们只要找到一个符合上述条件的最大数量的魔法方块集合就能找到一个能走魔法方块数量最多的路径。我们可以采用贪心策略的dp进行求解,首先使用贪心策略,对坐标进行排序,就可以保证一边是稳定递增的了,只要通过dp求出另一边的数据也是稳定递增的就能求出最优解了。

其完整代码如下:

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Scanner;

@SuppressWarnings("rawtypes")
class Node implements Comparable{//实现对象数组排序的接口
	long x=0;
	long y=0;
	Node(){
		this.x=x;
		this.y=y;
	}
	@Override
	public int compareTo(Object o) {
		if(o instanceof Node) {
			Node n=(Node) o;
			if(this.x >n.x) {
				return 1;
			}else if(this.x==n.x) {
				return 0;
			}else {
				return -1;
			}
		}
		return 0;
	}
}

public class Main {
	static Scanner sc =new Scanner(System.in);
	public static void main(String[]args) {
		long n=sc.nextInt();
		int k=sc.nextInt();
		long w1=sc.nextInt();
		long w2=sc.nextInt();
		BigInteger money=BigInteger.valueOf(0);
		Node node[]=new Node[k];
		if(2*w1<w2) {
			money=BigInteger.valueOf(2).multiply(BigInteger.valueOf(n)).multiply(BigInteger.valueOf(w1));
		}else {
			for(int i=0;i<k;i++) {
				node[i]=new Node();
				node[i].x=sc.nextInt();
				node[i].y=sc.nextInt();
			}
			Arrays.sort(node);
			long dp[]=new long[k];
			for(int i=0;i<k;i++) {
				dp[i]=1;
			}
			for(int i=1;i<k;i++) {
				for(int j=0;j<i;j++) {
					if(node[j].x<node[i].x&&node[j].y < node[i].y) {
						dp[i]=Math.max(dp[i], dp[j]+1);
					}
				}
			}
			long ans=0;
			for(int i=0; i<k; i++) {
				ans = Math.max(ans, dp[i]);
			}
			money=BigInteger.valueOf(ans).multiply(BigInteger.valueOf(w2)).add(BigInteger.valueOf(2).multiply(BigInteger.valueOf(n-ans)).multiply(BigInteger.valueOf(w1)));
		}
		System.out.print(money);
	}
}

这个算法的时间复杂度是O(k^2),其实还可以用二分查找进行优化,这里就不进行细说了,有兴趣的小伙伴们可以去学习学习。

你可能感兴趣的:(2020智算之道——复赛第二题java版)