BZOJ2792/POI2012 Well

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;
} 
發佈了39 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章