资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
石子游戏的规则如下:
地上有n堆石子,每次操作可选取两堆石子(石子个数分别为x和y)并将它们合并,操作的得分记为(x+1)×(y+1),对地上的石子堆进行操作直到只剩下一堆石子时停止游戏。
请问在整个游戏过程中操作的总得分的最大值是多少?
输入格式
输入数据的第一行为整数n,表示地上的石子堆数;第二行至第n+1行是每堆石子的个数。
输出格式
程序输出一行,为游戏总得分的最大值。
样例输入
10
5105
19400
27309
19892
27814
25129
19272
12517
25419
4053
样例输出
15212676150
数据规模和约定
1≤n≤1000,1≤一堆中石子数≤50000
拿到这个问题,我第一反应就是用集合。因为数组一旦建立,长度就固定了,合并起来会非常麻烦。这个问题的入手点还是挺简单的,求分公式已经告诉了:(x+1)*(y+1),既然是求总得分的最大值,那么只需每次都找石子数最多的两堆石子合并,得分一定是最多的。因此题目关键点在于如何找出一组数据中2个的最大的数。
关于找这俩最大的数,笔者走了点弯路。一开始是这样写的:
static void countScores(List<Long> list) { //计分操作
if(list.size()==1) {
return; //如果只剩一堆石子,结束递归
}
//找出块数最多的两堆石子
long max1=0; //块数最多的
long max2=0; //块数第二多的
int u=0; //u、v用于记录下标
int v=0;
for(int i=0;i<num.size();i++) {
if(max1<=num.get(i)) { //如果还有块数更多的
max2=max1; //当前第二多=原先最多
v=u; //下标也换过来
max1=num.get(i);
u=i;
}else if(max2<num.get(i)) {
max2=num.get(i);
v=i;
}
}
merge(max1, max2, u, v); //合并最多的两堆石子
maxScore=maxScore+(max1+1)*(max2+1); //记录得分
countScores(num);
}
虽然测试也通过了,但是不是觉得太麻烦?每次都要比较出找最大的两个数,还要记录下标。而且递归方法中套着for循环,算法复杂度也比较高。笔者虽然很菜,但上进心还是有的^ _ ^。不安于现状的我于是开始探寻优化方案。
经过一番思索和资料的搜集,终于有了新点子:先排序!排序的好处是处理合并过程的时候方便得多,每次只要合并集合中最后两个石堆就好了,因为最后两个一定是最大的两个。二次测试,果然看到了优化效果:
这里还要感谢这位朋友的博客给我的启发:点此链接可查看https://blog.csdn.net/weixin_43887915/article/details/105013845
最后,附上全部的源码吧,不足之处希望有大佬及时指正,感激不尽呀~~
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class StoneGame2 {
static List<Long> num; // 用于存放各堆石子块数
static long maxScore = 0; // 最大得分,范围用long型
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
num=new ArrayList<Long>();
for(int i=0;i<n;i++) {
num.add(sc.nextLong());
}
sc.close();
//排一下序更方便操作,可以先借助java的API自带方法,也可以自己写一个排序
Collections.sort(num);
countScores(num); //计分操作
System.out.println(maxScore);
}
//可以用递归或者while循环
static void countScores(List<Long> list) {
if(list.size()==1) {
return; //如果只剩一堆石子,结束递归
}
//由于已经排序,集合最后两个位置的数一定是最大的两个
long max1=list.get(list.size()-1);
long max2=list.get(list.size()-2);
maxScore=maxScore+(max1+1)*(max2+1); //记录得分
//将两个石块最多的石堆合为一个石堆,先删除旧的后添加新的
list.remove(list.size()-1);
list.remove(list.size()-1); //两次都是删掉最后一个
list.add(max1+max2);
countScores(num);
}
}
ps:取值范围很重要!数据相加后很容易超过int所表示的最大长度(10位),因此要用long型!!