题解:CF207B3 Military Trainings

题目传送门

题目大意

每个坦克有一个接收半径 \(r_i\),每次要从最前面的坦克把信号传给最后一个,最后一个在跑到前面来,重复执行,每两个坦克之间传输信号所需时间为 \(1\),求总的最小时间。

思路

考虑 \(n\le 300\) 的情况,我们可以使用模拟的方法。首先破环成链,把原序列复制一份接到序列后面,从 \(2\times n\) 的位置开始枚举,枚举到 \(n\),每次都模拟往前跳,找到能接收的范围内的接收半径最大的坦克,跳到他这里,直到跳到发送信号的坦克的位置。

时间复杂度 \(O(n^3)\)

考虑 \(n\le 10000\) 的情况,在先前的暴力的基础上,我们可以用线段树或者 st 表预处理出,每一个坦克 \(i\)\([i-r_i,i-1]\) 范围中能找到最大接收半径的坦克编号,然后每次跳的时候,直接跳到这个范围内的最大接收半径的坦克即可。

这样的话就可以做到在模拟的时候做到 \(O(n)\)。最终时间复杂度为 \(O(n^2)\)

考虑 \(n\le 250000\) 的情况,注意到跳跃可以用倍增处理,\(f_{i,j}\) 表示第 \(i\) 个坦克跳 \(2^j\) 次能跳到的坦克编号,那么每次都可以跳 \(2^j\) 次,大大缩减了跳跃的时间,做到 \(O(\log n)\) 的时间来模拟跳跃。

注意,每次要跳到正好差一次跳到到发送信号的坦克的位置,而且最后到达的位置 \(i\) 不是能跳一步就跳到目标位置的,因为是 \(i\) 的下一次跳跃的位置上的坦克的接收半径包含了目标位置,所以要跳两次。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=501010;
ll n,a[N],ans,t[N],fa[N][30],st[N][30];
void init(){
    for(ll i=1;i<=2*n;i++)st[i][0]=i;
    for(ll j=1;j<=29;j++){
        for(ll i=1;i<=2*n-(1<<j)+1;i++){
        	if(a[st[i][j-1]]<a[st[i+(1<<(j-1))][j-1]])st[i][j]=st[i][j-1];
        	else st[i][j]=st[i+(1<<(j-1))][j-1];
            
        }
    }
}
ll query(ll l,ll r){
    ll k=log2(r-l+1);
    if(a[st[l][k]]<a[st[r-(1<<k)+1][k]])return st[l][k];
    else return st[r-(1<<k)+1][k];
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        a[i+n]=a[i];
    }
    for(int i=1;i<=2*n;i++)a[i]=max(i-a[i],1ll);
    init();
    for(int i=1;i<=2*n;i++){
        fa[i][0]=query(a[i],i);
    }
    for(int j=1;j<=29;j++){
        for(int i=1;i<=2*n;i++){
            fa[i][j]=fa[fa[i][j-1]][j-1];
        }
    }
    for(int i=1;i<=n;i++){
        ll cnt=2;
        ll now=i+n-1;
        if(a[now]<=i){
            ans++;
            continue;
        }
        for(ll j=29;j>=0;j--){
            if(a[fa[now][j]]>i)cnt+=(1<<j),now=fa[now][j];
        }   
        ans+=cnt;
    }
    cout<<ans;
    return 0;
}
posted @ 2025-09-07 21:46  一班的hoko  阅读(7)  评论(0)    收藏  举报