Task
給出n個正整數X1,X2,…Xn,可以進行不超過m次操作,每次操作選擇一個非零的Xi,並將它減一。
最終要求存在某個k滿足Xk=0,並且z=max{|Xi - Xi+1|}最小。輸出最小的z和此時最小的k。
1<=n<=1,000,000, 1<=m<=10^18, Xi<=10^9.
Solution
求最小的k,我們可以通過二分把問題轉化爲驗證一個解是否可行.
驗證解k:
首先花最少的代價把序列構造爲max{|x[i]-x[i+1]|}<=k.
現在的問題就是找到使序列中某個數字改爲0的最小代價.
通過模擬可以發現,將某個數字x修改爲0,所影響的部分爲一個區間(l,r),左右部分是互補干擾的,可以分別算出.現在只考慮x的左邊部分,所修改的序列可以看爲以k爲公差的等差數列:k,2k,3k…知道某個數字t已經符合條件不用修改,那麼之後的數字也不必修改.
若從右到左枚舉x,可以確定t是單調不遞增的.當x+1改爲0時,x可能不變或縮小爲k;若x不變,那麼x+1對應的t就是x,那麼此輪x對應的t就是x+1;否則,在此輪x=0<=k,比x+1改爲0時(x=k)變得更小了,因此影響的範圍更大,t就會減小.
確定了單調性後,就可以利用等差數列求和公式求解啦.
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#include<iostream>
using namespace std;
const int M=1e6+5;
int A[M],B[M],n;
ll sum[M],m,L[M],R[M];
inline void rd(int &res){
res=0;char c;
while(c=getchar(),c<48);
do res=(res<<1)+(res<<3)+(c^48);
while(c=getchar(),c>=48);
}
ll sigma(int x){
return 1ll*x*(x+1)/2;
}
int chk(int x){//枚舉的最大的兩點之間的距離
int i,j,k=n+1,f=0,y;
ll t=0,mn=1e18;
B[1]=A[1];
for(i=2;i<=n;i++)B[i]=min(A[i],B[i-1]+x);
for(i=n-1;i>=1;i--)B[i]=min(B[i],B[i+1]+x);
for(i=1;i<=n;i++){
t+=A[i]-B[i],sum[i]=sum[i-1]+B[i];//處理前綴和
if(!B[i]&&!f)f=i;
}
if(t>m)return 0;
if(f)return f;
for(i=n;i>=1;i--){
while(k>=2&&B[k-1]>1ll*(i-k+1)*x)k--;//從[k,i]要改變->k*x,(k-1)*x,(k-2)*x...2x,x,0
L[i]=sum[i]-sum[k-1]-sigma(i-k)*x;
}
k=0;
for(i=1;i<=n;i++){
while(k<n&&B[k+1]>1ll*(k+1-i)*x)k++;//從[i,k]要改變->k*x,(k-1)*x,(k-2)*x...2x,x,0
R[i]=sum[k]-sum[i-1]-sigma(k-i)*x;
}
for(i=1;i<=n;i++){
if(L[i]+R[i]-B[i]+t<=m)return i;
}
return 0;
}
int main(){
int i,j,k,l=0,res,r=0,K;
cin>>n>>m;
for(i=1;i<=n;i++)rd(A[i]),r=max(r,A[i]);
while(l<=r){
int mid=(l+r)>>1,b;
b=chk(mid);
if(b){
res=mid;K=b;r=mid-1;
}else l=mid+1;
}
printf("%d %d\n",K,res);
return 0;
}