原文章https://www.luogu.org/blog/zzj/ti-xie-p4343-shoi2015-zi-dong-shua-ti-ji-post
100
100 分(满分)思路:
我会暴力!
- 枚举 nn 可能的最小值和最大值?显然会超时,时间复杂度约为 O((\sum_{i=1}^{n}x_i) \times n)O((∑i=1nxi)×n) 。显然会超时。
我会二分!
- 我们考虑使用二分来解决这道题。时间复杂度约为 O(n\;log_2\;n)O(nlog2n) 。是可以在规定的时间内完成本题的。
那么怎么二分呢?
我们可以用两次二分来完成这道题,一次求最小值,一次求最大值。下面我拿求最小值的那次二分做举例,我们设 midmid 为可能的最小值且 mid=(l+r) \div 2mid=(l+r)÷2 ,在这里我用了一个 susu 以及一个 tt 来分别表示当前写的代码的行数以及现在已经A掉了多少题目,若 t \leq mt≤m (我在这里题目中的 kk )则让 r'=mid-1r′=mid−1 ,且如果 t=mt=m ,就让 ans=midans=mid (即 nn 可能的最小值)否则让 l'=mid+1l′=mid+1 。直到 l>rl>r 时就跳出循环。下面上二分部分的代码:
long long l=1,r=inf;
while(l<=r)
{
long long mid=(l+r)/2;
long long su=0,t=0;
for(long long i=1;i<=n;i++)
{
su+=a[i];
if(su>=mid)
{
t++;
su=0;
}
if(su<0)
{
su=0;
}
}
if(t<=m)
{
r=mid-1;
if(t==m)
{
ans=mid;
}
}
else
{
l=mid+1;
}
}
if(ans!=-1)
{
printf("%lld ",ans);
ans=-1,l=1,r=inf;
while(l<=r)
{
long long mid=(l+r)/2;
long long su=0,t=0;
for(long long i=1;i<=n;i++)
{
su+=a[i];
if(su>=mid)
{
t++;
su=0;
}
if(su<0)
{
su=0;
}
}
if(t<=m-1)
{
r=mid-1;
}
else
{
l=mid+1;
if(t==m)
{
ans=mid;
}
}
}
printf("%lld",ans);
}
else
{
printf("-1");
}
- 一些坑点以及注意事项
二分的边界要设好,记住
while(l<=r)
不要写成while(l+1<=r)
或者是while(l<=r-1)
哦。rr 要设得足够大(例如999999999999999)。
记得要开 long\;longlonglong 哦。
下面上AC代码~
#include <cstdio>
long long a[1000001];
int main()
{
long long inf=999999999999999,ans=-1,n=0,m=0;
scanf("%lld %lld",&n,&m);
for(long long i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
long long l=1,r=inf;
while(l<=r)
{
long long mid=(l+r)/2;
long long su=0,t=0;
for(long long i=1;i<=n;i++)
{
su+=a[i];
if(su>=mid)
{
t++;
su=0;
}
if(su<0)
{
su=0;
}
}
if(t<=m)
{
r=mid-1;
if(t==m)
{
ans=mid;
}
}
else
{
l=mid+1;
}
}
if(ans!=-1)
{
printf("%lld ",ans);
ans=-1,l=1,r=inf;
while(l<=r)
{
long long mid=(l+r)/2;
long long su=0,t=0;
for(long long i=1;i<=n;i++)
{
su+=a[i];
if(su>=mid)
{
t++;
su=0;
}
if(su<0)
{
su=0;
}
}
if(t<=m-1)
{
r=mid-1;
}
else
{
l=mid+1;
if(t==m)
{
ans=mid;
}
}
}
printf("%lld",ans);
}
else
{
printf("-1");
}
return 0;
}