NOIP模拟-数字对

问题 C: 数字对
时间限制: 1 Sec 内存限制: 256 MB

题目描述
小H是个善于思考的学生,现在她又在思考一个有关序列的问题。 她的面前浮现出一个长度为n的序列{ai},她想找出一段区间[L, R](1 <= L <= R <= n)。 这个特殊区间满足,存在一个k(L <= k <= R),并且对于任意的i(L <= i <= R),ai都能被ak整除。这样的一个特殊区间 [L, R]价值为R – L。 小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。
输入
第一行,一个整数n. 第二行,n个整数,代表ai.
输出
第一行两个整数,num和val,表示价值最大的特殊区间的个数以及最大价值。 第二行num个整数,按升序输出每个价值最大的特殊区间的L.
样例输入
5
4 6 9 3 6
样例输出
1 3
2
提示
30%: 1 <= n <= 30 , 1 <= ai <= 32
60%: 1 <= n <= 3000 , 1 <= ai <= 1024.
80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.
100%: 1 <= n <= 500000 , 1 <= ai < 2 31 .
输出末尾有空格
题解:
这题很明显是RMQ,如果不会可以参考 此文
30分:枚举l,r,k,线性扫一遍O(n^4)
进一步思考
特殊区间的特点实际上就是区间最小值等于这个区间的GCD
不用枚举k变为 O ( n 3 )
再想想?
答案一定可以二分
因此我们二分答案len,判断长度为len的区间是否有可行的
时间复杂度 O ( n 2 l o g n )
O ( n ) 的时间复杂度求区间最小值和GCD显然是很浪费的
一个性质:一个集合多加一个数,那么新集合的GCD就是原来集合的GCD和新数的GCD
嗯?线段树!
那就是 O ( n l o g n l o g n ) 显然第四类数据是来卡你的
因此我们可以想到RMQ
令数组 g c d n [ i ] [ j ] 表示从第i个数起连续2^j个数中的GCD
令数组 m i n n [ i ] [ j ] 表示从第i个数起连续2^j个数中的最小值
O ( n l o g n ) 预处理
计算答案时 O ( 1 ) 查询
所以计算答案时时间复杂度也是 O ( n l o g n )
据说当时线段树连80分都不一定能拿到
C o d e :

#include<bits/stdc++.h>
#define N 500005
using namespace std;
int a[N],n,p,minn[N][20],gcdn[N][20],b[N];
int gcd(int a,int b)
{
    if(a>b)swap(a,b);
    if(a==0)return b;
    return gcd(b%a,a);
}
bool work(int x,int y)
{
    int k=log2(double(y-x+1));
    int nmin=min(minn[x][k],minn[y-(1<<k)+1][k]);
    int ngcd=gcd(gcdn[x][k],gcdn[y-(1<<k)+1][k]);
    if(nmin==ngcd)return true;else
        return false;
}
bool check(int len)
{
    bool flag=false;
    for(int i=1;i<=n-len+1;i++)
        if(work(i,i+len-1))
        {
            p=i;
            flag=true;
        }
    return flag;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        gcdn[i][0]=minn[i][0]=a[i];
    }
    for(int j=1;j<20;j++)
        for(int i=1;i<=n+1-(1<<j);i++)
        {
            minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
            gcdn[i][j]=gcd(gcdn[i][j-1],gcdn[i+(1<<(j-1))][j-1]);
        }
    int l=1,r=n;
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        if(check(mid))l=mid;else r=mid-1;
    }
    int tot=0;
    for(int i=1;i<=n-l+1;i++)
        if(work(i,i+l-1))
            b[++tot]=i;
    printf("%d %d\n",tot,l-1);
    for(int i=1;i<=tot;i++)
        printf("%d ",b[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_34531807/article/details/80932649
今日推荐