CF713E Sonya Partymaker

洛谷传送门 CF传送门

Description

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

Solution

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

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

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

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

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

[f_i=maxegin{cases}p_i+x &(p_i-1leq f_{i-1})\p_i&(p_i-x+1leq 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+1leq 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
",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
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/jasony/p/13930361.html