CF2019D Speedbreaker

题意

Link

一个数轴上有 \(1,2,\dots,n\)\(n\) 个点。第 \(1\) 秒时,你将从其中一个点开始染色,称为初始点,之后第 \(2,3,\dots,n\) 秒,你每秒可以将一个被染色的点左边或右边的点染色。每个点有一个时间限制,必须要在 \(a_i\) 秒前(包含第 \(a_i\) 秒)被染色,问有多少个初始点可以将所有点染色。

思路

法一

先考虑如何判断一组时间限制 \(a\) 是合法的:对于 \(t=1,2,3,\dots,n\),找到 \(a\) 中包含所有 \(a_i\leq t\) 的点的最小子区间 \([l_t,r_t]\),这个子区间的长度必须 \(\leq t\)。如果不满足这个条件那么初始点取在哪都是无解的。

接下来考虑如何寻找可行的初始点,一个初始点 \(i\) 可行等价于将 \(a_i\) 设为 \(1\) 之后这组 \(a\) 仍合法,因此对于 \(t=1,2,3,\dots,n\),可行的 \(i\) 只有可能在 \([r_t-t+1,l_t+t-1]\) 当中,因为如果 \(i\) 不在这之中,\(a_i\) 变为 \(1\) 后,\(\leq t\) 的最小子区间长度一定是 \(>t\) 的。于是我们对于所有的 \(t\) 求出 \([r_t-t+1,l_t+t-1]\) 的交集,就是可行的初始点集合。

法二

一个很直观的猜测是 \(\bigcap\limits_{i=1}^{n}[i-a_i+1,i+a_i-1]\) 就是有解的初始点,但是代入样例 5 6 4 1 4 5 后无解判成了有解,事实上猜测的大致方向是对的,我们只需要一点小修改这个说法就是正确的了:

交集中的点要么一起无解,要么一起有解,如果有解,那么交集内的点就是所有可行的初始点。

首先,显然从交集外的点出发一定无解,因为从它们出发至少有一个点是不可在规定时间前到达的。

那为什么交集中的点一起有解或无解呢?我们引入一个策略:

如果某个时刻,已染色点到某个未染色点的最小距离 \(\Delta d\) \(=\) 未染色点能被染色剩余的时间 \(\Delta t\),那么就朝这个未染色点的方向染色,否则可以任意往左或往右染色。

这个策略对于“从某个点出发可以染完所有点”是充分且必要的,也就是说,策略可行说明从这个点出发可以染完所有点,不可行说明不能染完所有点,不存在这个策略染不了而其他策略染的了的情况。

由于交集内任意两点之间的距离一定小于等于这两个点的 \(a_i\),因此我们从交集中的点出发,先把交集内的点全部染完是不和上述策略相违背的,因此从交集内任意一点出发都可以在第 \(len\) 秒到达相同状态,即交集都被染完的状态(\(len\) 为交集大小)。再判断这个状态能不能继续把交集外的点染完,能则说明交集内的点都可做为初始点,否则说明都不能做为初始点。

最后我们整理一下:判断给定的 \(a\) 有无解,如果有解则交集就是解,判断有无解的方法与法一相同。

代码

这里用的是法一的做法。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int MAXN=2e5+5;
int n,a[MAXN],lside[MAXN],rside[MAXN];
inline pii intersection(const pii& a,const pii& b){
    pii res;
    res.first=max(a.first,b.first);
    res.second=min(a.second,b.second);
    if(res.first>res.second) return make_pair(-1,-1);
    else return res;
}
inline void solve(){
    int minx=INT_MAX;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        minx=min(minx,a[i]);
    }
    memset(rside,0,sizeof(int)*(n+5));
    memset(lside,0x3f,sizeof(int)*(n+5));
    for(int i=1;i<=n;i++) rside[a[i]]=i;
    for(int i=n;i>=1;i--) lside[a[i]]=i;
    for(int i=minx;i<=n;i++) rside[i]=max(rside[i],rside[i-1]);
    for(int i=minx;i<=n;i++) lside[i]=min(lside[i],lside[i-1]);
    // for(int i=minx;i<=n;i++) cout<<i<<' '<<lside[i]<<' '<<rside[i]<<endl;
    for(int t=minx;t<=n;t++){
        if(rside[t]-lside[t]+1>t){
            cout<<0<<endl;
            return;
        }
    }
    pii ans=make_pair(1,n);
    for(int t=minx;t<=n;t++){
        ans=intersection(ans,make_pair(rside[t]-t+1,lside[t]+t-1));
        if(ans.first==-1 && ans.second==-1){
            cout<<0<<endl;
            return;
        }
    }
    cout<<ans.second-ans.first+1<<endl;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}
posted @ 2024-09-29 15:54  MessageBoxA  阅读(88)  评论(0编辑  收藏  举报