Cicada的序列
这道题貌似不太好做,但是我们发现除法最多只能除log(n)次,所以可以优化复杂度
具
体
的
,
原
来
右
端
点
需
要
O
(
n
)
枚
举
的
,
我
们
可
以
先
二
分
出
一
段
区
间
,
用
s
t
表
O
(
1
)
查
本
区
间
的
最
值
,
找
到
第
一
个
比
原
数
小
的
位
置
,
因
为
只
能
找
l
o
g
n
次
,
所
以
时
间
降
为
l
o
g
2
n
,
总
时
间
复
杂
度
为
O
(
n
l
o
g
2
n
)
具体的,原来右端点需要O(n)枚举的,我们可以先二分出一段区间,用st表O(1)查本区间的最值, 找到第一个比原数小的位置,因为只能找logn次,所以时间降为log^2n,总时间复杂度为O(nlog^2n)
具体的,原来右端点需要O(n)枚举的,我们可以先二分出一段区间,用st表O(1)查本区间的最值,找到第一个比原数小的位置,因为只能找logn次,所以时间降为log2n,总时间复杂度为O(nlog2n)
#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int N=300010;
int n,st[N][20],a[N];//
int find(int l,int r){
int len=log2(r-l+1);
return min(st[l][len],st[r-(1<<len)+1][len]);
}
int get(int l,int r,int val){
int ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(find(l,mid)<=val) ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
void print(ll x){
if(x>=10){
print(x/10);
}
putchar('0'+x%10);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){scanf("%d",&a[i]);st[i][0]=a[i];}
int up=log2(n);
for(int len=1;len<=up;len++){
for(int l=1;l+(1<<len)-1<=n;l++){
st[l][len]=min(st[l][len-1],st[l+(1<<(len-1))][len-1]);
}
}
ll ans=0;
for(int i=1;i<=n;i++){
int pos=i+1,val=a[i];ans+=val;
while(pos<=n){
int nxt=get(pos,n,val);
if(nxt==0){
ans+=1ll*(n-pos+1)*val;
break;
}
ans+=1ll*(nxt-pos)*val;
pos=nxt;
val=val%a[nxt];
}
}
print(ans);
}
除法最多logn次,所以用最值计算往往能优化