NOI 模拟赛 小奇的数列Ⅱ ST表+二分

小奇的数列Ⅱ

题目大意:

小奇总在数学课上思考奇怪的问题。
给定⼀个长度为M的数列,小奇定义,若⼀个区间[L,R](1≤L≤R≤n)满⾜:
存在⼀个k(L≤k≤R),使得对于任意的i(L≤i≤R),ai能被ak整除。
称这样的区间为可约的,其价值为R−L
小奇想知道数列中所有可约区间的最⼤价值x,以及价值为x的可约区间个数num,以及它们的左端点。

题目思路:

发现第一个性质:存在一个ak使得区间内 任意的ai都可以被ak整除,那么ak即为该区间的最小的那个数,或者说这个数就是区间的最大公约数。

其次,题目要求最大的长度,那么咱们去二分检测一下就可以了,二分枚举区间长度。判断在当前区间长度下,有没有合法区间,有合法区间就把区间扩大,看是否有更大的,没有就减小,最后二分枚举的答案即为,最长的符合要求的长度。

另外,每一个二分函数都会有一个judge函数,来判断我当前二分的值,合不合法在judge函数中,我们可以判断一下,当前区间的最小值是否可以被这个区间内所有数整除以,之后我们就可以知道存不存在合法区间。关于区间最小值,可以用ST表查询静态区间最小值,因为最小值可以合并所以可以用GCD,并且可以进一步优化,因为gcd也可以区间合并,所以可以进一步预处理出,[s,e]区间内的gcd与最小值,用O(1)的复杂度,去查询就可以把时间复杂度优化到最低,到时判断区间条件就可以改为:区间最小值==区间gcd。

附一下AC代码:

1.只查询最小值,判断区间合法 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1000000000000005;
const ll maxn=5e5+5;
const int mod=998244353;
ll n,m;
int num[maxn];
int st[maxn][30];
int lg[maxn];
int save[maxn];
void inint()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
    ll cnt=0;
    for(int i=1;i<=n;i++)
        lg[i]=lg[i/2]+1;
    for(int i=1;i<=n;i++)
        st[i][0]=num[i];
    for(int j=1;j<=25;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
ll query(ll x,ll y)
{
    ll len=lg[y-x+1]-1;
    return min(st[x][len],st[y-(1<<len)+1][len]);//为啥+1
}
int _judge(int s,int e,ll x)
{
    for(int i=s;i<=e;i++)
        if(num[i]%x!=0) return 0;
    return 1;
}
int judge(int x)
{
    for(int i=1;i+x-1<=n;i++)
    {
        int e=i+x-1;
        ll temp=query(i,e);
        if(_judge(i,e,temp)) return 1;
    }
    return 0;
}
void work()
{
    ll l=1,r=n;//二分长度
    ll ans=-1;
    while(l<=r)
    {
        ll mid=(l+r)/2;
        if(judge(mid))
        {
            ans=mid;
            l=mid+1;
        }
        else
            r=mid-1;
    }
    int res=0;
    for(int i=1;i+ans-1<=n;i++)
    {
        int e=i+ans-1;
        ll temp=query(i,e);
        if(_judge(i,e,temp))
            save[++res]=i;
    }
    printf("%d %lld\n",res,ans-1);
    for(int i=1;i<=res;i++) printf("%d ",save[i]);
}
int main()
{
    inint();
    work();
    return 0;
}
/**
***/

2. 预处理gcd,判断区间合法

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1000000000000005;
const ll maxn=5e5+5;
const int mod=998244353;
ll n,m;
int num[maxn];
int st[maxn][30];
int lg[maxn];
int save[maxn];
void inint()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
    ll cnt=0;
    for(int i=1;i<=n;i++)
        lg[i]=lg[i/2]+1;
    for(int i=1;i<=n;i++)
        st[i][0]=num[i];
    for(int j=1;j<=25;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
ll query(ll x,ll y)
{
    ll len=lg[y-x+1]-1;
    return min(st[x][len],st[y-(1<<len)+1][len]);//为啥+1
}
int _judge(int s,int e,ll x)
{
    for(int i=s;i<=e;i++)
        if(num[i]%x!=0) return 0;
    return 1;
}
int judge(int x)
{
    for(int i=1;i+x-1<=n;i++)
    {
        int e=i+x-1;
        ll temp=query(i,e);
        if(_judge(i,e,temp)) return 1;
    }
    return 0;
}
void work()
{
    ll l=1,r=n;//二分长度
    ll ans=-1;
    while(l<=r)
    {
        ll mid=(l+r)/2;
        if(judge(mid))
        {
            ans=mid;
            l=mid+1;
        }
        else
            r=mid-1;
    }
    int res=0;
    for(int i=1;i+ans-1<=n;i++)
    {
        int e=i+ans-1;
        ll temp=query(i,e);
        if(_judge(i,e,temp))
            save[++res]=i;
    }
    printf("%d %lld\n",res,ans-1);
    for(int i=1;i<=res;i++) printf("%d ",save[i]);
}
int main()
{
    inint();
    work();
    return 0;
}
/**
***/

可能是由于,预处理GCD的时候,常数太大的问题,第二个比一个慢200ms

发布了157 篇原创文章 · 获赞 146 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/103991054