码迷,mamicode.com
首页 > 其他好文 > 详细

数字对

时间:2019-02-03 10:33:40      阅读:159      评论:0      收藏:0      [点我收藏+]

标签:inpu   序列   check   整数   str   直接   details   代码   pac   

1月25日

Description

小H是个善于思考的学生,现在她又在思考一个有关序列的问题。
她的面前浮现出一个长度为n的序列 \(a_i\) ,她想找出一段区间[L, R] \((1\leq L\leq R\leq n)\) 。这个特殊区间满足,存在一个k \((L\leq k\leq R)\) ,并且对于任意的i \((L\leq i\leq R)\)\(a_i\) 都能被 \(a_k\) 整除。这样的一个特殊区间[L, R]价值为 \(R - L\) 。小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。

Input

第一行,一个整数n.

第二行,n个整数,代表 \(a_i\) .

Output

第一行两个整数,numval,表示价值最大的特殊区间的个数以及最大价值。

第二行num个整数,按升序输出每个价值最大的特殊区间的L.

Sample Input

输入1:

5
4 6 9 3 6

输入2:

5
2 3 5 7 11

Sample Output

输出1:

1 3
2

输出2:

5 0
1 2 3 4 5

Data Constraint

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}\).

Solution

比赛的时候我看了一眼题目,然后闭眼打了一个退化的暴力二分,直接不管了。然后60分滚蛋。可以欣赏一下。

#include<cstdio>
#include<algorithm>
using namespace std;

int n,a[500002],num,val;
long long sum[500002];
int l,r,mid,minn;
int ans[500002];

void fmin(int x,int y)
{
    minn=2e9;
    for(int i=x;i<=y;i++)
        minn=min(minn,a[i]);
}
bool permod(int x,int y,int z)
{
    for(int i=x;i<=y;i++)
        if(a[i]%z!=0) return false;
    return true;
}
bool check(int x)
{
    for(int i=1;i<=n-x+1;i++)
    {
        fmin(i,i+x-1);
        if((sum[i+x-1]-sum[i-1])%minn!=0) continue;
        if(!permod(i,i+x-1,minn)) continue;
        return true;
    }
    return false;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    l=0;r=n+1;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    for(int i=1;i<=n-l+1;i++)
    {
        fmin(i,i+l-1);
        if((sum[i+l-1]-sum[i-1])%minn!=0) continue;
        if(!permod(i,i+l-1,minn)) continue;
        num++;ans[num]=i;
    }
    val=l-1;
    printf("%d %d\n",num,val);
    for(int i=1;i<=num;i++)
        printf("%d ",ans[i]);
    return 0;
}

咳咳,好了说正解。。。有两种。

  1. \(O(nlogn)\)
  2. \(O(n)\)非常玄学,不会打

首先用RMQ算法,维护最小值。这个算法非常优秀,预处理 \(O(nlogn)\) ,查询却是 \(O(1)\) 。额我实在是懒得引用了,自己查来复习。刘汝佳的书上有。

https://www.cnblogs.com/YSFAC/p/7189571

\(by\) Fugtemypt


RMQ习题:https://jzoj.net/junior/#contest/home/1532

然后就是最懵逼的地方--用RMQ求区间gcd。因为如果一个区间符合题意,那它的最小值就是这个区间的gcd(PS:STL自带一个gcd,函数名称叫__gcd)。

https://blog.csdn.net/cj1064789374/article/details/85217974

\(by\) _ Jim _

这个代码

Memory Code Length
122.27 MB 1352 bytes

放心食用(第一次写RMQ,贼丑)

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

int n,a[500002],num,val;
long long sum[500002];
int l,r,mid,minn,gcdn;
int ans[500002];
int f[500002][30];
int st[500002][30];

void rmq_gcd_init()
{
    int lim=log2(n);
    for(int i=1;i<=n;i++)
        st[i][0]=a[i];
    for(int j=1;j<=lim;j++)
        for(int i=1;i<=n;i++)
            if(i+(1<<j)-1<=n)
                st[i][j]=__gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
void gcd(int x,int y)
{
    int k=log2(y-x+1);
    gcdn=__gcd(st[x][k],st[y-(1<<k)+1][k]);
}
void rmq_init()
{
    int lim=log2(n);
    for(int i=1;i<=n;i++)
        f[i][0]=a[i];
    for(int j=1;j<=lim;j++)
        for(int i=1;i<=n;i++)
            if(i+(1<<j)-1<=n)
                f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        
}
void rmq(int x,int y)
{
    int k=0;
    while((1<<(k+1))<=y-x+1) k++;
    minn=min(f[x][k],f[y-(1<<k)+1][k]);
}
bool check(int x)
{
    for(int i=1;i<=n-x+1;i++)
    {
        rmq(i,i+x-1);
        gcd(i,i+x-1);
        if(minn==gcdn)
            return true;
    }
    return false;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    rmq_init();
    rmq_gcd_init();
    l=0;r=n+1;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    for(int i=1;i<=n-l+1;i++)
    {
        rmq(i,i+l-1);
        gcd(i,i+l-1);
        if(minn==gcdn)
            ans[++num]=i;
    }
    val=l-1;
    printf("%d %d\n",num,val);
    for(int i=1;i<=num;i++)
        printf("%d ",ans[i]);
    return 0;
}

关于\(O(n)\)

就算我记下来思路也写不出来

首先我们枚举k(题中的k),然后以 \(O(n)\) 预处理出k之前的能被k整除的最长区间的长L[k],然后同理预处理出R[k]。接下来,你懂得。

数字对

标签:inpu   序列   check   整数   str   直接   details   代码   pac   

原文地址:https://www.cnblogs.com/mocking-jimmy/p/10349672.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!