题目:牛牛的揠苗助长(牛客)
题目描述(感觉这样直接粘贴不太好):有一块长度为n(1<=n<=105)的菜地,每块菜地菜的高度为a[i](i=1,…,n,1<=a[i]<=109)。菜的生长有一个周期,恰好为n,且第i天a[i%n]菜地长高1。现有魔法每天可使用一次:使某一块菜地长高或长矮1,或者不变也行。问最少多少天能使所有菜地的菜的高度一样。
输入:第一行一个整数n,第二行n个整数表示a[i]。
输出:一个整数,表示需要天数的最小值。
例:输入:3
1 2 3
输出:1
分析:
一个经典版本:水稻不会生长,问最少操作。别的不多说,直接上结论:
1).最优策略一定是将所有水稻都向某一个水稻看齐,也就是至少有一颗水稻是不动的。(特例:如1,6。那么当x在[a[n/2],a[n/2+1]]范围内都有最优解,即这里x在[1,6]范围内答案都是6,所以也可假设a[1]=1不动)
2).令a[i]不变,ans=(i*a[i]-sum(1,i))+(sum(i+1,n)-(n-i)*a[i])//sum(1,i)表示sum[i]-sum[1-1]即a[i]a[i]的和(sum为前缀和数组,另外别忘了先要对数组a从小到大排序)。第一个括号表示将1i项变为a[i]的操作数,第二个括号表示将i+1n项变为a[i]的操作数。然后遍历i从1n,取最小操作数。复杂度为O(n)。
//这是什么知识不知,希望下次遇到不会栽。
针对本题(水稻生长怎么办):首先无限长时间后我们终于将他们调整到了同一高度,能否保持?能,将每天生长的摁下去不就得了。
也就是说,此问题在答案天数上具有单调性,比如最少ans天,那么ans+1天也一定能。所以我们二分答案,在已知答案的基础上计算最少需要的天数是否等于这个答案,等于就ok了。
已经确定了天数,那么第i科水稻一定生长了ans+(i<=ans%n),每个都加了ans相当于没加(加了反而会爆掉)。所以ans天时b[i]=a[i]+(i<=ans%n)。然后就当作时水稻不生长的情况就ok。只要最小操作次数<=答案天数,就是合法的。
AC代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=100005;
ll a[MAXN],b[MAXN],sum[MAXN];
int n;
ll get_sum(int l,int r){
return sum[r]-sum[l-1];
}
bool ck(ll days){
for(int i=1;i<=n;++i) b[i]=a[i]+(i<=days%n);
sort(b+1,b+1+n);
for(int i=1;i<=n;++i) sum[i]=sum[i-1]+b[i];
ll need=2e18;
for(int i=1;i<=n;++i){
need=min(need,b[i]i-get_sum(1,i)+get_sum(i+1,n)-b[i](n-i));
}
return days>=need;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
ll l=1,r=1e18,ans=1e18;
while(l<=r){
ll mid=(l+r)>>1;
if(ck(mid)){
ans=mid;
r=mid-1;
}
else l=mid+1;
}//二分的思想还是很简单的,也超级有用。感觉就是还有一点点小细节不太懂
cout<<ans<<endl;
return 0;
}