尺取法

尺取法

eg:
给一个正数数组,求最短的sum比x大的一个区间

假设我们枚举左端点,每次往右加入,直到大于x,那么会有很多重复的计算,这种时候我们可以用尺取法降低时间复杂度

把已经计算的区间看成尺子,加入当前尺子sum不够,就把右端点向右延伸,直到大于x为止。当大于后,我们不是重置尺子左端点重新开始右延,而是把左端点往右缩

如果缩了还是满足大于x,当下情况就也可以成为ans。
缩到了不满足时,在重复右延步骤

时间复杂度直接从n*n变成了n

例题

原题Thor vs Frost Giants

题意

给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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值