尺取法
eg:
给一个正数数组,求最短的sum比x大的一个区间
假设我们枚举左端点,每次往右加入,直到大于x,那么会有很多重复的计算,这种时候我们可以用尺取法降低时间复杂度
把已经计算的区间看成尺子,加入当前尺子sum不够,就把右端点向右延伸,直到大于x为止。当大于后,我们不是重置尺子左端点重新开始右延,而是把左端点往右缩。
如果缩了还是满足大于x,当下情况就也可以成为ans。
缩到了不满足时,在重复右延步骤
时间复杂度直接从n*n变成了n
例题
题意:
给n个数的数组,选出最短的满足区间乘积%n==0的区间
假如上面说的,每个数提供给区间和的就是数本身,那么这里每个数提供的是gcd(n,a[i]),因为只有这部分才有助于区间乘积%n==0,所以尺子伸的时候,tmp(有用的数的集合)*=gcd,缩的时候tmp/=gcd,只要在某段区间tmp%n==0,那么这段区间即可能成为ans
但是,tmp一直*gcd会出现什么情况?
假设n=1e5,那么会有多少gcd?tmp哪怕是long long也存不下了
所以,用一个数组timer代表tmp(把tmp差分乘素因子的乘积)。首先素数筛,然后把n拆分了存在stan数组里,用于比较。后面就处理timer,假设gcd==4,那么就是2个2,素数筛出来pri[1]是2,pri[2]是3,所以timer[1]+=2;
当然为了避免每次都从第一个素数到最后一个素数扫一遍,可以用另外一个数组存下有用的素数(n的素因子),之后每次扫的时候就找这些素数就好了
代码:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#define N 100009
#define D long long
#define mmm(a,b) memset(a,b,sizeof(a))
using namespace std;
int pri[N/2],now;//存素数,pri[1]=2,pri[2]=3
bool xp[N];
int timer[N/2];//当前范围内有用的素因子分别有多少
int stan[N/2];//N的素因子数量,用来对比
int yin[N/2],now1;//存n的素因子(即有用的素因子)分别是多少 eg:n==10 yin[1]=2,yin[2]=5
void init_pri(){//素数筛
mmm(xp,0);xp[0]=xp[1]=1;
for(int i=2;i<=N-9;i++){
if(!xp[i])pri[++now]=i;
for(int j=1;j<=now&&pri[j]*i<=N-9;j++){
xp[pri[j]*i]=1;
}
}
}
int n,a[N];
void init_yin(){//选出有用素因子,并初始化stan
int tmp=n;
for(int i=1;i<=now&&pri[i]<=tmp;i++){
if(tmp%pri[i])continue;
yin[++now1]=i;
while(tmp%pri[i]==0)tmp/=pri[i],stan[i]++;
}
}
void add(int y,int f){//在尺取法尺子移动时,添加或删除质因子,f=1加,-1减
for(int i=1;i<=now1&&pri[yin[i]]<=y;i++){
while(y%pri[yin[i]]==0){
y/=pri[yin[i]];timer[yin[i]]+=f;
}
}
}
int judge(){//判断timer是不是满足了stan
int ans=1;
for(int i=1;i<=now1;i++){
if(timer[yin[i]]<stan[yin[i]]){
ans=0;break;
}
}
return ans;
}
int main(){
init_pri();
while(scanf("%d",&n)!=EOF){
mmm(timer,0);mmm(stan,0);now1=0;
int ans=N,ansl,ansr;
for(int i=1;i<=n;i++)scanf("%d",a+i);
int st=1,en=1;
init_yin();
add(__gcd(n,a[1]),1);
while(st<=n){
if(ans==1)break;
if(judge()){//可以了就删除前面的
if(en-st+1<ans)ans=en-st+1,ansl=st,ansr=en;
add(__gcd(n,a[st]),-1);st++;
}
else{//不行就继续从后面加进
en++;if(en>n)break;
add(__gcd(n,a[en]),1);
}
}
if(ans==N)printf("-1\n");
else printf("%d %d\n",ansl-1,ansr-1);
}
}