对于M段最小和问题的拙见
作者:互联网
最近在做算法设计课设,遇见了一道动态规划的例题
题目:给定n个整数组成的序列,现在要求将序列分割为m段,每段子序列中的数在原序列中连续排列。如何分割才能使这m段子序列的和的最大值达到最小?
样例输入
1 1
10
样例输出
10
样例输入
9 3
9 8 7 6 5 4 3 2 1
样例输出
17
解:
最小m段和问题:
使用dp[i][j]放置将前i个数分成j段的最小最大值
所以dp[i][1]就是前i个数的和,dp[i-1][1]+a[i];
当j>1的时候,加定前k个数为j-1段,从k~i为第j段
所以前j-1段的最小最大值为:dp[k]j-1
最后一段为:dp[i][1]-dp[k]1
这两个数种取最大值,当所有分段情况完成之后,选出最小值作为dp[i][j]的值
所以递推公式为:
dp[i][j] = min{max{dp[i][1]-dp[k][1],dp[k][j-1]}}
题目理解:可以看作是清洁工问题,一共有3个清洁工,共清洁5个房间,每个房间用时分别为5,4,3,2,1每个清洁工只能清洁自己和相邻的房间,不能跳着清理(比如既清洁5又清洁1,这是不允许的),合理分配每个清洁工清洁的房间,然总清洁时间最少,最佳分配结果为 5|4|3,2,1 派一个清洁工清洁5,派一个清洁工清洁4,再派一个清洁工去清洁3,2,1这样总清洁时间最小为6(多线程同时清洁,最长的清洁时间既为总清洁时间)
代码如下:
private static int [] numbers;
private static int [][]f;
private static final int MAX=100000;
public static void solve(int n, int m){//算法主要方法
int i,j,k,temp,max;
for (i=1;i<=n;i++){
f[i][1]=f[i-1][1]+numbers[i];//依次在f[i][1]存入前i个数的和
}
for (j=2;j<=m;j++){//根据分段次数进行划分,从划分两段开始
for (i=j;i<=n;i++){
for (k=1,temp=MAX;k<i;k++){
max=max(f[i][1]-f[k][1],f[k][j-1]);//选出前j-1段与最后一段最大的
if(temp>max)
temp=max;//每次划分完后选小的段和更新
}
f[i][j]=temp;
}
}
}
public static int max(int a,int b){//比较大小
return Math.max(a, b);
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("前请输入序列长度:");
int n=sc.nextInt();//输入n,序列长度
System.out.println("请输入要分成的段数:");
int m=sc.nextInt();//输入m,要分成的段数
if(n<m||n==0){//如果输入的序列长度小于要分成的段数或者序列长度为0都为输入有误
System.out.println("输入有误");
return;//输入有误时终止运行
}
numbers=new int[n+1];//按照序列长度构造整数数组
f=new int[n+1][m+1];
System.out.println("请输入该序列:");
for (int i=1;i<=n;i++){
numbers[i]=sc.nextInt();//依次输入整数
}
solve(n,m);
System.out.println(f[n][m]);
}
}
这样统一看肯定不会特别的明显,其中最重要的就是solve()方法,它是此问题的解决的关键
我们以输入n=5,m=3,整数序列为5,4,3,2,1为举例来看
在属性中定义的f[n][m]大小的数组用来储存每次分割的最小最大值,solve()方法中
for (i=1;i<=n;i++){ f[i][1]=f[i-1][1]+numbers[i];//依次在f[i][1]存入前i个数的和 }
在f[n][m]第一行依次存入前i个数的和
\ 1 2 3 4 5
1 5 9 12 14 15 前i个数的和
2 0 0 0 0 0
3 0 0 0 0 0
4 0 0 0 0 0
5 0 0 0 0 0
剩下未填入的为默认初始值为0
for (j=2;j<=m;j++){//根据分段次数进行划分,从划分两段开始 for (i=j;i<=n;i++){ for (k=1,temp=MAX;k<i;k++){ max=max(f[i][1]-f[k][1],f[k][j-1]);//选出前j-1段与最后一段最大的 if(temp>max) temp=max;//每次划分完后选小的段和更新 } f[i][j]=temp; } }
每次分割时i决定参分割的数字个数,k决定前几个数字被分割,例如j=2,i=2,k=1时,分割情况是这样5|4||3,2,1,其中||右边的数字不参与分割,例如j=2,i=4,k=2时,分割情况是这样的5,4|3,2||1
其中最外层循环控制分割次数,从二次分割依次到m次分割,第二层循环是按照前几个参与分割的数目不同列出所有分割的结果,第三层循环是将序列分为两段,一个是前j-1段,一个是第j段,找出最大值,每次划分后选出这些最小的最大值并更新temp,在第二层循环的末尾将temp储存在该划分下面
\ 1 2 3 4 5
1 5 9 12 14 15 前i个数的和 未分割j=1
2 0 5 7 9 9 一次分割的 分割一次j=2
3 0 0 0 0 0
4 0 0 0 0 0
5 0 0 0 0 0
此时,将二次分割的储存在f[n][m]中,若进行多次分割也是在这个的基础上进行分割,且使用数据只用查询f[n][m]即可,类似于动态规划的备忘录结构
\ 1 2 3 4 5
1 5 9 12 14 15 前i个数的和 未分割j=1
2 0 5 7 9 9 一次分割的 分割一次j=2
3 0 0 5 5 6 二次分割的 分割两次j=3
4 0 0 0 0 0
5 0 0 0 0 0
此时分段段数最小最大值为f[n][m]=6
这种做法只适用数据较小的情况,当数据较大时应该采用二分搜索查找的方式,这就是后话了。
标签:分割,temp,对于,拙见,最大值,清洁,最小,int,dp 来源: https://www.cnblogs.com/AJAXXJ/p/14975718.html