当前位置: 首页 > news >正文

【算法 | 实验8】分配最小页数(数组划分和最大值最小化问题)

文章目录

  • 题目
  • 问题分析与算法设计思路
    • 思路1:类似枚举的分治(暴力)
    • 思路2:二分法
  • 算法实现(C++)
    • 思路1实现
    • 思路2实现
  • bug记录
    • 1、子问题对最大值没有实现最小化
    • 2、保存的最大值是局部的
  • 运行结果
  • 算法分析

题目

分配最小页数 (学校OJ 357题)
给定n本书的页数,m名学生。这些书按页数的升序排列成一个序列。每个学生都被分
配去读一些序列中连续的书。设分配给第i个学生的书籍的总页数为Pi,1<=i<=m。找出
优化的分配方式,使得最大的Pi最小。用分治法求解。
输入描述
第一行输入t,表示t个测试用例
对于每一个测试用例第一行输入n和m,表示书的数量和学生的数量
第二行按升序输入n个整数,表示书的页数。
输出描述
t行,表示t个测试用例的解
样例输入
2
4 2
12 34 6790
15 7
12 34 67 90 95 103 150 165 170 198 201 235 240 245 251
样例输出
113
436

问题分析与算法设计思路

思路1:类似枚举的分治(暴力)

基本思路:我们这样划分问题,不妨使第一个同学看书 x 页;而剩余的书和同学则构成一个原问题的子问题,使剩余同学中看书最多的同学看了 y 页。那么所有同学中的最大页数 z = m a x ( x , y ) z = max(x,y) z=max(x,y)
而对于剩余的书和同学,我们继续划分为一个同学和其它剩余的同学。直到只剩下一个同学时,就不可再分,组合这些子问题的解将得到最初原问题的解。

思路2:二分法

在思路1中,我们以同学为主要对象进行,通过改变给同学分配的书的数量,来搜索解空间。

基本思路:这里对问题引入一种新的描述,以方便理解。我们把每个同学看作一个,而每本书看作一个物品,书的页数就是物品的大小。每个桶有一个最大容量,分配书籍就是将物品一个个放入桶中,当一个桶放不下时,就将物品放到下一个桶中。那么问题就变成了,要找一个最小桶容量,而恰好能将所有的物品都放进桶中。

因此,这里问题的主要对象是桶的容量

算法过程对桶容量进行二分搜索,找到最小桶容量

首先确定一个桶容量的大致范围。不妨令第 i 个物品的大小为 a [ i ] a[i] a[i],所有物品大小和为 sum,则桶容量的初始区间为 [ a [ m ] , s u m ] [a[m],sum] [a[m],sum](m 为物品数量)。

搜索:
设初始搜索区间为 [ f i r s t , e n d ] [first,end] [first,end]

  • 先取中间值 m i d = ( f i r s t + e n d ) / 2 mid=(first+end)/2 mid=(first+end)/2,然后对容量 mid 进行判断
  • 恰好合适,可以放下所有物品,但桶容量再减小 1 就放不下了。
  • 桶小了,放不下所有物品,则搜索区间的右边 [ m i d + 1 , e n d ] [mid+1,end] [mid+1,end]
  • 桶大了,能放下所有物品,但不是恰好合适,则搜索区间左边 [ f i s r t , m i d − 1 ] [fisrt,mid-1] [fisrt,mid1]
  • 递归求解

判断桶能否放下所有物品:

  • 遍历物品序列,逐个放入桶中。若需要桶的数量大于 m,则桶容量小了。

算法实现(C++)

思路1实现

#include<iostream>
using namespace std;
const int Big = 8848;//用作极大值 
int renshu;//学生人数 

 
int anser(int m,int n,int l,int r,int a[])//表示m堆书,n个学生在分摊之后,返回学生所看书的最大页数 
{
//	cout<<"m="<<m<<" n="<<n<<" l="<<l<<endl;
	int answer = 0;
	if(n == 1)
	{
		int sum = 0;
		for(int i = l; i <= r; i++)
		{
			sum += a[i];
		}
		answer = sum;
		goto label;
	}
	if(m == n)
	{
		answer = a[r];
		goto label;
	}
	if(m < n)
	{
		answer = Big;
		goto label;
	}
	if(m > n)
	{
		int minOfMax = Big;
		int sum = 0;
		for(int i = l; i <= m - n + l; i++)
		{
//			cout<<"-------------------i="<<i<<"  m-n+l= "<<m-n+l<<endl;
			sum += a[i];
			int t = anser(m-i+l-1, n-1, i+1, r, a);
			int maxNum = max(sum, t);//原问题分成两部分,取最大值 
			
			if(maxNum == 13)
			{
				system("pause");
			}
			
			minOfMax = min(minOfMax, maxNum);
		}
		answer = minOfMax;
	}
label:
	return answer;
}

int main()
{
	int t,m,n;
	cin>>t;//t表示测试案例数 
	for(int j=0;j<t;j++)
	{
		cin>>m>>n;//m表示书堆的数目,n表示学生数目 
		renshu=n;
		int *a=new int[m+1];
		for(int i=0;i<m;i++)
			cin>>a[i];	
		int answer = anser(m,n,0,m-1,a);//递归求解 
		cout<<"answer: "<<answer<<endl;
		delete [] a;
	}
	return 0;
} 
/*测试数据
2
4 2
12 34 67 90
15 7
12 34 67 90 95 103 150 165 170 198 201 235 240 245 251
*/

思路2实现

运行

在这里插入图片描述

代码
为了方便展示运行效果,代码中嵌入了一些输出中间变量值的语句。而且在 main 函数中仅安排了一个测试用例的输入。

#include<iostream>
using namespace std;

int TwoPointSearch(int f, int e, int a[], int n, int m);
bool Can(int a[], int n, int x, int m);


int main(){
	int n = 0;//物品数量
	int m = 0;//桶的数量 
	int sum = 0;//物品大小之和 
	cin >> n >> m;
	int *a = new int[n];
	for(int i = 0; i < n; i++)
	{
		cin >> a[i];
		sum += a[i];
	}
	int answer = TwoPointSearch(a[n-1], sum, a, n, m);
	cout<<"answer:"<<answer<<endl; 
	return 0;
}

int TwoPointSearch(int f, int e, int a[], int n, int m)
{
	//返回:能装下的最小桶容量
	cout << "arange:" << f << " " << e;
	
	int mid = (f + e) / 2;
	bool can = Can(a, n, mid, m);
	
	cout << "  can:" << can;
	cout << "  mid:" << mid << endl;
	
	if(can)
	{
		int can_smaller = Can(a, n, mid-1, m);
		if(! can_smaller)
		{
			return mid;
		}
		else
		{
			return TwoPointSearch(f, mid-1, a, n, m);
		}
	}
	else
	{
		return TwoPointSearch(mid+1, e, a, n, m);
	}
}

bool Can(int a[], int n, int x, int m)
{
	//放得下吗?
	//n:物品数,m:桶数 
	int flag = false; //to test 
	
	if(x < a[0])
	{
		return false;
	}
	
	int need = 0;//需要桶的数量
	int sum = 0;//一个桶已经装了多少 
	
	for(int i = 0; i < n; i++)
	{
		if(sum + a[i] <= x)
		{
			sum += a[i];
		}
		else
		{
			sum = a[i];
			need++;
		}
		if(need + 1 > m)
		{
			return false;
		}
	} 
	
	return true;
}

bug记录

1、子问题对最大值没有实现最小化

问题分析
answer这个全局变量很不好,破坏了递归的逻辑。

本来要分治,前面一部分就是k,然后后面是剩余部分的最小化最大值。

代码里是用什么实现最大值的最小化的?就是answer,但answer之会在递归的第一层更新。

也就是说,对分治后的后面一段子数组,并没有实现最大值的最小化。

解决方案(见思路1实现)
改用将 answer 作为函数内的局部变量的写法。

问题代码版本

#include<iostream>
using namespace std;
int answer=100000;
int maxsize=1;
int renshu;
int anser(int m,int n,int l,int r,int a[])//表示m堆书,n个学生在分摊之后,返回学生所看书的最大页数 
{
//	cout<<"m="<<m<<" n="<<n<<" l="<<l<<endl;
	if(n==1)//如果只有一个学生,就直接返回a数组中的值之和 
	{
		int sum=0;
		for(int i=l;i<=r;i++)
		{
			sum+=a[i];
		}
		return sum;
	}
	else if(m==n)//若书堆数和学生数相同,因为要保证每个学生都有书看,而且数组a已经排好序了,所以直接返回最后一个数即可 
	{
		return a[r];
	}
	else if(m<n)//此种情况会导致有学生没有书看,所以返回零(这个不大确定,我是乱写的) 
	return 0;
	else if(m>n)//当书堆数大于学生人数时 
	{
		int k=0;
		for(int i=l;i<=m-n+l;i++)//表示列举可能的组合 ...这里不知道咋解释 
		{
			k+=a[i];
			maxsize=max(k,anser(m-i+l-1,n-1,i+1,r,a));
			if(n==renshu)
			{
				answer=min(answer,maxsize);
			}
		}
	}
}

int main()
{
	int t,m,n;
	cin>>t;//t表示测试案例数 
	for(int j=0;j<t;j++)
	{
		cin>>m>>n;//m表示书堆的数目,n表示学生数目 
		renshu=n;
		int *a=new int[m+1];
		for(int i=0;i<m;i++)
		cin>>a[i];	
		anser(m,n,0,m-1,a);//递归求解 
		cout<<answer;
		delete [] a;
		answer=100000;
	}
	return 0;
 } 

2、保存的最大值是局部的

问题分析
可以对比思路1实现。原来b也通过answer返回了,到上一层求b又有一次max的筛选,得到的才是最大值。
直接将b保存下来,只经过了与本层k的比较,就相当只比较了两个人的看书量,不是该分配情况下所有人看书最多的那一个。

b数组应该存的是若干个最大值,然后通过排序最小化最大值。现在你代码并没有保证存的b是个“最大值”

问题代码版本

#include<iostream>
#include<vector>
#include<cstdlib>
#include<algorithm>
using namespace std;
int renshu,book;
vector<int>v;
vector<int>::iterator it;
int maxsize=100000;
void anser(int m,int n,int l,int r,int a[],int &b)//表示m堆书,n个学生在分摊之后,返回学生所看书的最大页数 
{
	if(n==1)
	{
		int sum=0;
		for(int i=l;i<=r;i++)
		{
			sum+=a[i];
		}
		b=sum;
		return ;
	}
	else if(m==n)
	{
		b=a[r];
		return ;
	}
	else if(m<n)
	{
		b = 8848;
		return;
	}
	else if(m>n)
	{
		int k=0;
		for(int i=l;i<=m-n+l;i++)
		{
			if(n==1)
			break; 
			else if(m==n)
			break;
			k+=a[i];
			int temp;
			anser(m-i+l-1,n-1,i+1,r,a,temp);
			b=max(k,temp);
			
			v.push_back(b);
		}
		return;
	}
}

int main()
{
	int t,m,n,b=0,sum=0;
	cin>>t;//t表示测试案例数 
	for(int j=0;j<t;j++)
	{
		sum=0;
		cin>>m>>n;//m表示书堆的数目,n表示学生数目 
		book=m;
		renshu=n;
		int *a=new int[m+1];
		for(int i=0;i<m;i++)
		{
			cin>>a[i];
			sum+=a[i];
		}
		if(n==1)
		cout<<sum<<endl;
		else if(m==n)
		cout<<a[m-1]<<endl;
		else
		{
			anser(m,n,0,m-1,a,b);//递归求解 
			sort(v.begin(),v.end());
			cout<<*v.begin()<<endl;
		}	
		delete [] a;
	}
	return 0;
 } 

运行结果

在这里插入图片描述

算法分析

思路1
本来枚举的时间复杂度应为 O ( 2 n ) \Omicron (2^n) O(2n) (n 为书数量),每本书都有分给当前同学和分给下一个同学两种选择。不过还有剪枝的优化。

思路2
采用二分搜索的时间复杂度为 O ( l o g ( m ) ) \Omicron (log(m)) O(log(m)) (m 为桶数),但每次判断桶容量是否合适都是在遍历物品序列,遍历的时间复杂度为 O ( n ) \Omicron(n) O(n) (n 为物品数量)。

因此总的时间复杂度应为: O ( n ∗ l o g ( m ) ) \Omicron(n*log(m)) O(nlog(m))


14天阅读挑战赛

相关文章:

  • rsync远程同步+inotify监控
  • 学会这个Python技能,就可以跟excel说再见了
  • 【漏洞复现-showdoc-文件上传】​vulfocus/showdoc-cnvd_2020_26585
  • 改进YOLOv7系列:首发结合最新Transformer视觉模型MOAT结构:交替移动卷积和注意力带来强大的Transformer视觉模型,超强的提升
  • 【jQuery案例】手风琴
  • 毕业设计 基于大数据的共享单车数据分析与可视化
  • PointNet:基于深度学习的3D点云分类和分割模型
  • 计算器的混合运算
  • HttpRunner
  • 『百日百题 · SQL篇』备战面试,坚持刷题(一)
  • 【Nginx】认识与基本使用 Nginx 实现反向代理、配置负载均衡
  • SpringBoot项目启动后执行指定方法的四种实现
  • 通过 Docker 容器配置 Jenkins 集成 SonarQube
  • 回顾——PCB绘制
  • linux后台运行程序命令screen
  • 「译」Node.js Streams 基础
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • Cumulo 的 ClojureScript 模块已经成型
  • emacs初体验
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • fetch 从初识到应用
  • iOS 颜色设置看我就够了
  • Java IO学习笔记一
  • JDK 6和JDK 7中的substring()方法
  • learning koa2.x
  • Linux中的硬链接与软链接
  • Map集合、散列表、红黑树介绍
  • 爱情 北京女病人
  • 仿天猫超市收藏抛物线动画工具库
  • 给第三方使用接口的 URL 签名实现
  • 开源SQL-on-Hadoop系统一览
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • No resource identifier found for attribute,RxJava之zip操作符
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • #AngularJS#$sce.trustAsResourceUrl
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (java)关于Thread的挂起和恢复
  • (solr系列:一)使用tomcat部署solr服务
  • (笔试题)分解质因式
  • (差分)胡桃爱原石
  • (强烈推荐)移动端音视频从零到上手(上)
  • (转)scrum常见工具列表
  • (转)setTimeout 和 setInterval 的区别
  • (转)visual stdio 书签功能介绍
  • (转)创业的注意事项
  • .net mvc部分视图
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .NET 材料检测系统崩溃分析
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • [<死锁专题>]
  • [20170705]lsnrctl status LISTENER_SCAN1
  • [AAuto]给百宝箱增加娱乐功能
  • [BUUCTF 2018]Online Tool
  • [C/C++]_[初级]_[关于编译时出现有符号-无符号不匹配的警告-sizeof使用注意事项]