题解 洛谷 P4343 [SHOI2015]自动刷题机
自己的第一道\(SHOI\)题目\(QAQ\)
我们来看题,题意大概是:让你求出正整数\(n\),使得将给定数列分成\(k\)段,按照题目所述方法计算后每一段的和都大于等于\(n\)的最大值和最小值。
如果我们只要计算最大值,这题就可以转化为一个与\(P1182 数列分段 Section II\)相似的二分模型。
需要注意的是,当用\(mid\)得出的答案大于等于\(m\)时,我们应该把\(l\)移至\(mid\)以得到最大值,并用\(ans\)记录。因为\(check(mid)>m\)时答案一定是偏小的,而\(=\)时不一定是最大值。
代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int w=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
return w*h;
}
int n,m,x[100010],l,r,ans;
int check(int mid){
int cnt=0,sum=0;
for(int i=1;i<=n+1;i++){
sum+=x[i];
if(sum<0)sum=0;
if(sum>=mid)cnt++,sum=0;
}
return cnt;
}
signed main(){
n=read();m=read();
for(int i=1;i<=n;i++)x[i]=read(),r+=abs(x[i]);
r++;
while(l+1<r){
int mid=l+r>>1;
if(check(mid)>=m)l=mid,ans=l;
else r=mid;
}
printf("%lld",ans);
return 0;
}
接下来考虑最小值:由于我们已经求出了最大值,所以最小值一定在0~ans之间(ans是之前求出的最大值),可以继续使用二分求解.特别的,我们仅当check(mid)=m时才把r移至mid,因为刚开始r必定是在答案区间内的,所以check(r)无论如何都不会>m,最后输出r即可。
对于无解情况:如果在第一次求解最大值时记录的ans为0,必定无解。如果第二次求解后最小值r大于最大值ans,同样无解
部分细节会在代码中说明。
AC代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int w=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
return w*h;
}
int n,m,x[100010],l,r,ans;
int check(int mid){//check函数使用int方便两次二分的判断
int cnt=0,sum=0;
for(int i=1;i<=n+1;i++){
sum+=x[i];
if(sum<0)sum=0;//题目中说明了若已写代码行数小于删除的行数则视为全部删除
if(sum>=mid)cnt++,sum=0;//写完一题则舍弃之前的代码。
}
return cnt;
}
signed main(){
n=read();m=read();
for(int i=1;i<=n;i++)x[i]=read(),r+=abs(x[i]);//因为k可能=1,此时n可能为总代码长度。
r++;//由于个人习惯l+1<r,所以二分之前r必须加1(具体可百度各种二分写法,一些博客中会有说明)
while(l+1<r){//个人习惯写法
int mid=l+r>>1;
if(check(mid)>=m)l=mid,ans=l;
else r=mid;
}
if(ans==0){
puts("-1");
return 0;
}//第一种无解情况
l=0;//由于最后输出的是r,所以如果l=1,而答案n正好是1,会得不到正确答案
while(l+1<r){
int mid=l+r>>1;
if(check(mid)==m)r=mid;
else l=mid;
}
if(r>ans){
puts("-1");
return 0;
}//第二种无解情况
printf("%lld %lld",r,ans);
return 0;
}