ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

对于M段最小和问题的拙见

2021-07-06 10:31:29  阅读:202  来源: 互联网

标签:分割 temp 对于 拙见 最大值 清洁 最小 int dp


最近在做算法设计课设,遇见了一道动态规划的例题
题目:给定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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有