【Codeforces 1032G】Chattering
ST 表,倍增。
题目大意:
有 $n$ 个人围成一圈,要传递消息。第 $i$ 个人会在 $1$ 个时刻内把消息传递到距离他不超过 $a_i$ 的人。
问第 $0$ 时刻分别只有 $1,2,3,\ldots,n$ 知道消息时,各需要多少时间才能把消息传递到所有人。
题解:
考虑序列上怎么做。
容易发现,每一时刻传递到信息的人一定是连续的,记录这段区间为 $[l,r]$。
那么下一次传递,我们就让 $[l,r]$ 中能往左、右传递最远的两个点进行传递。
这个可以用 ST 表维护区间最值,然后 $O(1)$ 单次跳。
但是这样复杂度还是 $O(n^2)$。
容易想到倍增。我们开 $\log n$ 个 ST 表,分别维护一个点跳 $2^n$ 次能跳到的最左、右端点(中间可能切换方向更优所以要同时维护)。
然后查询的时候也倍增即可。
时空复杂度 $O(n\log^2n)$。
考虑在圈上怎么做。
将序列变成 $3$ 倍长度,建 ST 表。
然后从中间开始倍增。如果某次跳完之后,区间长度大于等于 $n$ 则说明完全覆盖。以此来倍增即可。
时空复杂度 $O(n\log^2 n)$。
但是 $3$ 倍空间开不下。
由于三份的查询是类似的,所以可以只开 $1$ 倍空间,然后每次查询就分三段进行即可。
这样能正好卡进 $256\texttt{MB}$。
Code:
#include<iostream>
#include<algorithm>
#include<cstring>
#define lg2(x)(31^__builtin_clz(x))
using namespace std;
const int N=100005;
int n,a[N];
struct Right{
int mx[N],st[17][N];
inline int&operator[](const int&x){return mx[x];}
void init(){
memcpy(*st,mx,sizeof mx);
for(int i=1;i<17;++i)
for(int j=1;j+(1<<(i-1))<=n;++j)
st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
inline int query(int l,int r){
if(l>r)return-(1<<30);
const int g=lg2(r-l+1);
return max(st[g][l],st[g][r-(1<<g)+1]);
}
inline int Q(int l,int r){
return max({query(max(l,1),min(r,n)),n+query(max(l,n+1)-n,min(r,2*n)-n),-n+query(max(l,-n+1)+n,min(r,0)+n)});
}
}R[17];
struct Left{
int mn[N],st[17][N];
inline int&operator[](const int&x){return mn[x];}
void init(){
memcpy(*st,mn,sizeof mn);
for(int i=1;i<17;++i)
for(int j=1;j+(1<<(i-1))<=n;++j)
st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
inline int query(int l,int r){
if(l>r)return 1<<30;
const int g=lg2(r-l+1);
return min(st[g][l],st[g][r-(1<<g)+1]);
}
inline int Q(int l,int r){
return min({query(max(l,1),min(r,n)),n+query(max(l,n+1)-n,min(r,2*n)-n),-n+query(max(l,-n+1)+n,min(r,0)+n)});
}
}L[17];
void init(){
for(int i=1;i<17;++i){
L[i-1].init();
R[i-1].init();
for(int j=1;j<=n;++j)
L[i][j]=L[i-1].Q(L[i-1][j],R[i-1][j]),
R[i][j]=R[i-1].Q(L[i-1][j],R[i-1][j]);
}
L[16].init(),R[16].init();
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
if(n==1)return puts("0"),0;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1;i<=n;++i)
R[0][i]=i+a[i],L[0][i]=i-a[i];
init();
for(int i=1;i<=n;++i){
int ans=0,l=i,r=i;
for(int j=16;~j;--j){
int nL=L[j].Q(l,r),nR=R[j].Q(l,r);
if(nR-nL+1<n)ans|=1<<j,l=nL,r=nR;
}
printf("%d ",ans+1);
}
return 0;
}