数字对

Posted mocking-jimmy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数字对相关的知识,希望对你有一定的参考价值。

1月25日

Description

小H是个善于思考的学生,现在她又在思考一个有关序列的问题。
她的面前浮现出一个长度为n的序列 (a_i) ,她想找出一段区间[L, R] ((1leq Lleq Rleq n)) 。这个特殊区间满足,存在一个k ((Lleq kleq R)) ,并且对于任意的i ((Lleq ileq 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
",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
",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]。接下来,你懂得。


以上是关于数字对的主要内容,如果未能解决你的问题,请参考以下文章

golang代码片段(摘抄)

JavaScript - 代码片段,Snippets,Gist

JavaScript笔试题(js高级代码片段)

手写数字识别——基于全连接层和MNIST数据集

需要对特定 R 代码片段的解释

LeetCode810. 黑板异或游戏/455. 分发饼干/剑指Offer 53 - I. 在排序数组中查找数字 I/53 - II. 0~n-1中缺失的数字/54. 二叉搜索树的第k大节点(代码片段