「SOL」Landmarks(Codeforces)

上課的時候覺得聽懂了系列(而後發現其實啥都沒懂)ide


# 題面

一個質量均勻分佈的橫樑由 \(n+2\) 個高度相同的柱子支撐。柱子的位置能夠抽象在數軸上,從左到右編號爲 \(0\sim n+1\),第 \(i\) 個柱子在 \(x_i\) 處,它的承重能力是 \(d_i\);特別的,柱子 \(0\)\(n+1\) 的承重能力爲 \(+\infty\)spa

對於相鄰的兩根柱子 \(u\)\(v\),每根柱子承受它們之間的橫樑的重量的一半。若一個柱子承受的重量超過其能力,它就會坍塌(消失)。code

如今你要指定一個位置加一根柱子(承重能力本身指定),使得最後除了柱子 \(0\)\(n+1\),還有柱子留下;特別的,若是加柱子的位置本來有柱子,原來的柱子就會被新柱子替換掉;而且你加的柱子的位置、承重能力都是非負實數。求加的柱子的最小承重能力。get

數據規模:\(1\le n\le10^5\)\(0\le x_i,d_i\le10^9\)input


# 解析

先判斷是否一開始就能夠有中間的柱子留下(判斷方法後面寫),若是是,則答案爲 \(0\)。下面的解析默認忽略這種狀況。string

因爲一段橫樑的重量由相鄰兩個柱子分擔,容易發現加入一根橫樑的目的是阻止最終與它相鄰的兩個柱子坍塌,而且顯然加入的柱子不會坍塌。it

因而不妨決策加入柱子後,最終與這個柱子相鄰的柱子是哪兩個,記爲 \(L,R\)。這樣就能夠分紅先後綴考慮,即分別考慮加入的柱子左邊和右邊各自留下了哪些柱子。明顯的,\(L,R\) 是否合法以及加入的柱子的承重能力僅取決於 \(L,R\)io

對於柱子 \(i\) 計算 「假設 \(i\) 不會倒,則最終的方案中,\(i\) 的左邊是哪一個柱子」,記爲 flef[i];同理,右邊的柱子記爲 frig[i]。考慮如何計算 flef[i]。能夠從左到右計算每根柱子,用棧維護當前有哪些柱子(棧頂就是如今剩下的柱子中最右邊的)。計算 flef[i] 時,判斷棧頂的柱子會不會坍塌,直到找到不會坍塌的柱子即爲 flef[i],而後把 \(i\) 入棧。class

若是計算出來 flef[n+1] 不是 \(0\),則說明不用加柱子就已經合法。方法

同理求得 frig[i] 後,能夠求得,若是 \(i\) 不坍塌

  • \(i\) 右邊至少在哪一個位置要有柱子:\(\frac{x-flef_i}2\le d_i\to x\le 2d_i+flef_i\)
  • \(i\) 左邊至少在哪一個位置要有柱子:\(\frac{frig_i-x}2\le d_i\to x\ge frig_i-2d_i\)

若是 \(L,R\) 知足

\[\begin{cases} 2d_L+flef_L>x_L\\ frig_R-2d_R< x_R\\ 2d_L+flef_L\ge frig_R-2d_R \end{cases} \]

則能夠在 \(L,R\) 之間放一根柱子使該方案合法。前兩個不等式保證了 \(L\) 左邊的重量不會使 \(L\) 坍塌、\(R\) 右邊的重量不會使 \(R\) 坍塌,第三個不等式保證了存在一種在 \(L,R\) 之間放柱子的方案使得 \(L,R\) 都不坍塌。另外,能夠看出不管在 \(L,R\) 之間哪一個位置加柱子,加的柱子的承重能力都是 \(\frac{x_R-x_L}2\),因而只須要找合法的最小的 \(x_R-x_L\)

雙端點問題能夠考慮枚舉一個端點,好比 \(R\)\(R\) 肯定後,就至關因而找最大的知足條件的 \(L\)。容易發現,若是 \(L_2>L_1\) 而且 \(flef_{L_2}+2d_{L_2}\ge flef_{L_1}+2d_{L_1}\),則 \(L_1\) 必定就沒有用了。因而能夠從左到右掃描,用單調棧維護 \(flef_i+2d_i\) 單減的序列,對於 \(R\) 在單調棧上二分 \(flef_i+2d_i\ge frig_R-2d_R\) 的最大 \(i\)


# 源代碼

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

template<class T>inline T Rint(T &r){
    int b=1,c=getchar();r=0;
    while(c<'0' || '9'<c) b=c=='-'? -1:b,c=getchar();
    while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar();
    return r*=b;
}
const int N=1e5+10;

int n,m;
int aryx[N],stk[N],flef[N],frig[N];
long long aryd[N];

int main(){
    // freopen("input.in","r",stdin);
    Rint(n);
    for(int i=0;i<=n+1;i++) Rint(aryx[i]);
    for(int i=1;i<=n;i++) Rint(aryd[i]);
    aryd[0]=aryd[n+1]=1e9+7;
    //flef[i] 假設i不會坍塌,i向左最近能reach到哪一個位置
    //stack 中儲存的是「假設i不會坍塌,哪些柱子會留下來」
    stk[m=1]=0;
    for(int i=1;i<=n+1;i++){
        while(m>1 && 2*aryd[stk[m]]<aryx[i]-aryx[stk[m-1]])
            m--;
        flef[i]=aryx[stk[m]];
        stk[++m]=i;
    }
    //frig[i] 向右同理
    stk[m=1]=n+1;
    for(int i=n;~i;i--){
        while(m>1 && 2*aryd[stk[m]]<aryx[stk[m-1]]-aryx[i])
            m--;
        frig[i]=aryx[stk[m]];
        stk[++m]=i;
    }
    //若是n+1能reach到中間的柱子,則中間的柱子就能夠留下
    if(flef[n+1]){printf("%f\n",0.0);return 0;}
    stk[m=1]=0;
    int ans=aryx[n+1]-aryx[0];
    for(int i=1;i<=n+1;i++){
        int nL=1,nR=m;
        //二分左邊最近的合法區間與i有交的點
        while(nL+1<nR){
            int nM=(nL+nR)>>1;
            if(flef[stk[nM]]+2*aryd[stk[nM]]>=frig[i]-2*aryd[i]) nL=nM;
            else nR=nM;
        }
        int it=0; //0必然合法
        if(flef[stk[nR]]+2*aryd[stk[nR]]>=frig[i]-2*aryd[i]) it=stk[nR];
        else if(flef[stk[nL]]+2*aryd[stk[nL]]>=frig[i]-2*aryd[i]) it=stk[nL];
        //i自己左合法(還能夠向左延伸)
        if(frig[i]-2*aryd[i]<aryx[i])
            ans=min(ans,aryx[i]-aryx[it]);
        //i自己右合法(還能夠向右延伸)
        if(flef[i]+2*aryd[i]>aryx[i]){
            while(m && 2*aryd[i]+flef[i]>=2*aryd[stk[m]]+flef[stk[m]]) m--;
            stk[++m]=i;
        }
    }
    printf("%f\n",ans/2.0);
    return 0;
}

THE END

Thanks for reading!

\[\begin{split} 「\ &青山遠去萬里遙\ 蘇瀾城外雨瀟瀟\\ &孤舟一葉問船家\ 何日重到蘇瀾橋\ 」\\ ——&\text{《何日重到蘇瀾橋》By 泠鳶yousa} \end{split} \]

> Linked 何日重到蘇瀾橋-Bilibili

相關文章
相關標籤/搜索