P1181 数列分段Section I
P1182 数列分段 Section II
题目描述
对于给定的一个长度为N的正整数数列A-iA−i,现要将其分成M(M≤N)M(M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列4 2 4 5 142451要分成33段
将其如下分段:
[4 2][4 5][1][42][45][1]
第一段和为66,第22段和为99,第33段和为11,和最大值为99。
将其如下分段:
[4][2 4][5 1][4][24][51]
第一段和为44,第22段和为66,第33段和为66,和最大值为66。
并且无论如何分段,最大值不会小于66。
所以可以得到要将数列4 2 4 5 142451要分成33段,每段和的最大值最小为66。
输入格式
第11行包含两个正整数N,M。
第22行包含NN个空格隔开的非负整数A_iAi,含义如题目所述。
输出格式
一个正整数,即每段和最大值最小为多少。
输入输出样例
输入 #1复制
5 3 4 2 4 5 1
输出 #1复制
6
说明/提示
对于20\%20%的数据,有N≤10N≤10;
对于40\%40%的数据,有N≤1000N≤1000;
对于100\%100%的数据,有N≤100000,M≤N, A_iN≤100000,M≤N,Ai之和不超过10^9109。
二分答案: 在所有可行解中 二分猜 最优解(类似于猜数字游戏)
而且在所有可行解中 存在单调性和 存在上下界 则可根据单调大了还是小了 从而最快找到最优解
博客
我们考虑把这个词典从中间分开,看一下中间那一页的主要单词都是啥,然后去判断我要找的单词应该在左半部分还是右半部分,再去那一部分考虑怎么找就好了。同样的,在另一部分也是要进行划分并且判断的操作。这样一直进行下去,便能很快的找到答案,而且根本不需要翻过整个词典来。
可以证明,如果一页一页的找,最多要找 nn 次,但是用这个方法,最多找floor(log2n)floor(log2n)次。
我们把这个方法叫做“二分答案”。顾名思义,它用二分的方法枚举答案,并且枚举时判断这个答案是否可行。但是,二分并不是在所有情况下都是可用的,使用二分需要满足两个条件:
1.有上下界
2.区间有单调性
二分答案应该是在一个单调闭区间上进行的。也就是说,二分答案最后得到的答案应该是一个确定值,而不是像搜索那样会出现多解。二分一般用来解决最优解问题。刚才我们说单调性,那么这个单调性应该体现在哪里呢?
可以这样想,在一个区间上,有很多数,这些数可能是我们这些问题的解,换句话说,这里有很多不合法的解,也有很多合法的解。我们只考虑合法解,并称之为可行解。考虑所有可行解,我们肯定是要从这些可行解中找到一个最好的作为我们的答案, 这个答案我们称之为最优解。
最优解一定可行,但可行解不一定最优。我们假设整个序列具有单调性,且一个数x为可行解,那么一般的,所有的 x'(x'<x)x′(x′<x)都是可行解。并且,如果有一个数y是非法解,那么一般的,所有的 y'(y'>y)y′(y′>y) 都是非法解。
那么什么时候适用二分答案呢?注意到题面:使得选手们在比赛过程中的最短跳跃距离尽可能长。如果题目规定了有“最大值最小”或者“最小值最大”的东西,那么这个东西应该就满足二分答案的有界性(显然)和单调性(能看出来)。
示例代码 #include <bits/stdc++.h>//万能头文件 using namespace std; int n, m, x, f[100001], p, l, r, mid, sum, ans, maxn;//f数组前缀和,sum为分割数列的段数,ans为最终答案,maxn为数列中的最大值 int main(){ scanf ( "%d %d", &n, &m ); for ( int i = 1; i <= n; i++ ) scanf ( "%d", &x ), f[i] = f[i-1] + x, maxn = max ( maxn, x );//前缀和与找出最大值 l = maxn, r = f[n];//定义二分的区间 while ( l <= r ){ mid = ( l + r ) >> 1;//二分套路不解释 p = 1, sum = 1;//初始就有1段,p为当前分段位置 for ( int i = 2; i <= n; i++ ){ if ( f[i] - f[p-1] > mid )//如果第i~p-1个数的和大于mid p = i, sum++;//当前位置为i,段数+1 } if ( sum > m ) l = mid + 1;//如果段数大于m,搜后半段 else r = mid - 1, ans = mid;//符合要求,把mid赋值给ans,搜前半段 } printf ( "%d\n", ans );//输出答案 return 0; }
AC代码:
#include<bits/stdc++.h> using namespace std; int n,m,maxx=0,sum=0; int num[100005]; int ans(int mm) { int res=0; int summ=0; for(int i=0;i<n;i++) { summ+=num[i]; if(summ>=mm) { res++; summ=0; continue; } else if(summ&&summ+num[i+1]>mm&&i<n-1) { res++; summ=0; } } if(summ>0) res++; return res; } int main() { cin>>n>>m; for(int i=0;i<n;i++) { scanf("%d",&num[i]); sum+=num[i]; maxx=max(maxx,num[i]); } int l=maxx,r=sum; while(l<r) { int mid=l+(r-l)/2; int aa=ans(mid); if(aa>m) { l=mid+1; } else { r=mid; } } cout<<l<<endl; }