CF713E Sonya Partymaker

洛谷传送门 CF传送门

Description

在一个长度为 \(m\) 的环上有指定的 \(n\) 个点,每个点可以选一个方向(左或右)延伸出一条长度为 \(x\) 的线段,问覆盖这个环的最小 \(x\)

Solution

因为要求最小的 \(x\) ,所以可以考虑二分答案。

那么我们怎么 \(\operatorname{check}\) 呢?

因为是在一个环上,所以用熟悉的方法,先拆环成链,然后再返过来看环。

如果在一个链上,我们就可以用DP了。

\(f_i\) 为第 \(i\) 个点能覆盖的最远位置, \(p_i\) 为第 \(i\) 个点的位置,那么可以得出转移方程:

\[f_i=\max\begin{cases}p_i+x &(p_i-1\leq f_{i-1})\\p_i&(p_i-x+1\leq f_{i-1}<p_i-1)\end{cases} \]

第一个代表 \(i-1\) 能覆盖到 \(i\) 的位置,所以 \(i\) 可以往右走;第二个是 \(i-1\) 覆盖不到 \(i\) 但是 \(i\) 往左走能使得 \(i\) 之前被都被覆盖,所以 \(i\) 走的最远的位置就是原来的位置。

但是这样会有个问题, \(i-1\)\(i\) 之间的位置如果可以用 \(i-2\) 往左走来覆盖,我们没有考虑这种情况。那么此时 \(i-1\) 就可以往右走,状态方程就会变成:

\[f_{i}=\max(f_i,p_{i-1}+x) (p_{i}-x+1\leq f_{i-2}) \]

状态转移就完了。

嗯? \(i-1\)\(i\) 之间的位置能被 \(i+1\) 覆盖,怎么办。

那什么情况 \(i+1\) 会往左覆盖呢?就是 \(f_{i}<p_{i+1}-1\) ,所以如果 \(i\) 能往右,那 \(i-1\) 往右显然更优,所以是包含在上面的方程中的( ̄▽ ̄)"

那么回过来考虑环怎么办——发现可能 \(1\)\(2\) 之间的这一段是由 \(n\) 来覆盖的,那这样就不能转移了。

为了避免这种情况,我们就将最长的那一条边破开,并这条边作为二分的上界,那么现在的 \(n\) 就不能往右覆盖 \(1\) 了。

然后将 \(1\) 往左和往右都DP一次即可。

Code

#include<bits/stdc++.h>
#define ll long long

using namespace std;
const int N=200010;
ll n,m,a[N],p[N],f[N],l,r,ans;

inline ll read(){
    ll x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    return x*f;
}

inline void init(){
    int now=1;
    for(int i=2;i<=n;i++)
        if(a[i+1]-a[i]>a[now+1]-a[now]) now=i;
    r=a[now+1]-a[now]-1;
    for(int i=1;i<=n;i++) p[i]=a[i+now];
    ll tmp=p[1];
    for(int i=1;i<=n;i++) p[i]-=tmp;
}

inline bool check(ll x){
    f[1]=0;
    for(int i=2;i<=n;i++){
        f[i]=f[i-1];
        if(f[i-1]>=p[i]-1) f[i]=max(f[i],p[i]+x);
        if(f[i-1]>=p[i]-x-1) f[i]=max(f[i],p[i]);
        if(i>2&&f[i-2]>=p[i]-x-1) f[i]=max(f[i],p[i-1]+x);
    }
    if(f[n]>=m-x-1) return 1;
    f[2]=max(p[2],x);
    for(int i=3;i<=n;i++){
        f[i]=f[i-1];
        if(f[i-1]>=p[i]-1) f[i]=max(f[i],p[i]+x);
        else if(f[i-1]>=p[i]-x-1) f[i]=max(f[i],p[i]);
        if(f[i-2]>=p[i]-x-1) f[i]=max(f[i],p[i-1]+x);
    }
    if(f[n]>=min(m-1,m+p[2]-x-1)) return 1;
    return 0;
}

int main(){
    m=read(); n=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        a[i+n]=a[i]+m;
    }
    if(n==1){
        printf("%lld\n",m-1);
        return 0;
    }
    init();
    while(l<=r){
        ll mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2020-11-05 10:32  jasony_sam  阅读(158)  评论(0编辑  收藏  举报