题目意思: 把一个正整数序列划分成m个连续的子序列(每一个整数恰好属于每一个序列),设第i个序列的和为s(i),现在我们的任务就是让所有的S(i)的最大值尽量小。
解题思路: 1:最大值最小化问题:把一个正整数序列划分成m个连续的子序列(每一个整数恰好属于每一个序列),设第i个序列的和为s(i),现在我们的任务就是让所有的S(i)的最大值尽量小。
2:最大值最小化问题是一种很常见的优化目标。我么考虑一下新的问题就是,能否把输入的序列划分成m个连续的子序列,使得每一个子序列的S(i)都不超过x,那么只要找到这个最小的x就是S(i)的最大值,然后再去处理这个序列即可
3:怎么找到这个x,我么知道x肯定大于等于这个序列的最大值m,并且小于等于sum,那么我们可以利用二分查找,假设mid = X0,那么如果这个时候不满足那么这个x肯定比mid大,所以left = mid。如果这个时候满足,那么x可能等于这个mid或小于mid,所以right = mid。所有数之和为sum,那么二分的时间为O(LogSum)。
4:划分原序列,由于题目规定了,在多个满足的情况下尽量让第一个小,第二个小.......。那么要满足这个情况,只能是从后面往前分,并且由于分成几部分已经被限制住还有每一个人至少有一本书,那么我们还要控制当前分的时候,是否有剩下的书本大于等于人数。
5:注意使用long long保险
代码:
#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <vector> #include <cstdio> #include <stack> #include <queue> #include <set> using namespace std; #define MAXN 510 int t; int m , k; long long p[MAXN]; long long sum , max_num; //判断这个值是否满足 int judge(int a){ int i , cnt; long long tmp_sum; cnt = 0 ; tmp_sum = 0; for(i = 0 ; i < m ; i++){ if(cnt >= k) return 0;//如果分成的部分多于k,直接返回0 if(tmp_sum + p[i] > a){ cnt++ ; tmp_sum = 0; } tmp_sum += p[i]; } if(tmp_sum > a || cnt >= k) return 0;//注意这里也要在判断一下 return 1; } //输出 void output(int a){ long long tmp_sum = 0; int cnt = 0; int vis[MAXN]; memset(vis , 0 , sizeof(vis));//用来标记某个位置后面是否有“/” for(int i = m-1 ; i >= 0 ; i--){ if(tmp_sum+p[i] <= a && i >= (k-cnt-1))//同时满足两个条件才能加 tmp_sum += p[i]; else{ tmp_sum = p[i] ; cnt++; if(cnt < k) vis[i] = 1;//这里必须是cnt<k } } printf("%lld" , p[0]); if(vis[0]) printf(" /"); for(int i = 1 ; i < m ; i++){ printf(" %lld" , p[i]); if(vis[i]) printf(" /"); } printf("\n"); } void solve() { long long left , right , mid; int flag; left = max_num ; right = sum; //迭代二分查找 while(1){ if(right == left) break; mid = (left+righ2:最大值最小化问题是一种很常见的优化目标。我么考虑一下新的问题就是,能否把输入的序列划分成m个连续的子序列,使得每一个子序列的S(i)都不超过x,那么只要找到这个最小的x就是S(i)的最大值,然后再去处理这个序列即可t)/2; flag = judge(mid); if(flag) right = mid;//这个地方是因为如果flag为1说明这个最小值小于或等于mid ,所以不能 right = mid - 1 而是right = mid; else left = mid+1;//这个地方是因为如果flag为0说明这个最小值肯定是大于mid,所以left = mid + 1; } output(left);//最小值就是left或right,进行输出 } int main() { //freopen("input.txt" , "r" , stdin); scanf("%d" , &t); while(t--){ scanf("%d%d" , &m , &k) ; sum = 0 ; max_num = 0; for(int i = 0 ; i < m ; i++){ scanf("%lld" , &p[i]); sum += p[i]; if(max_num < p[i]) max_num = p[i]; } solve(); } return 0; }