Codeforces 359D Pair of Numbers【思维+二分+区间Gcd+区间最小值RMQ】

D. Pair of Numbers
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Simon has an array a1, a2, ..., an, consisting of n positive integers. Today Simon asked you to find a pair of integers l, r (1 ≤ l ≤ r ≤ n), such that the following conditions hold:

  1. there is integer j (l ≤ j ≤ r), such that all integers al, al + 1, ..., ar are divisible by aj;
  2. value r - l takes the maximum value among all pairs for which condition 1 is true;

Help Simon, find the required pair of numbers (l, r). If there are multiple required pairs find all of them.

Input

The first line contains integer n (1 ≤ n ≤ 3·105).

The second line contains n space-separated integers a1, a2, ..., an (1 ≤ ai ≤ 106).

Output

Print two integers in the first line — the number of required pairs and the maximum value of r - l. On the following line print all l values from optimal pairs in increasing order.

Examples
Input
5
4 6 9 3 6
Output
1 3
2 
Input
5
1 3 5 7 9
Output
1 4
1 
Input
5
2 3 5 7 11
Output
5 0
1 2 3 4 5 
Note

In the first sample the pair of numbers is right, as numbers 6, 9, 3 are divisible by 3.

In the second sample all numbers are divisible by number 1.

In the third sample all numbers are prime, so conditions 1 and 2 are true only for pairs of numbers (1, 1), (2, 2), (3, 3), (4, 4), (5, 5).


题目大意:

给你N个数,让你找到所有的区间【L,R】,使得R-L是最大的值而且区间内有一个数可以被其他所有数整除.


思路:


1、找到一个区间【L,R】使得其中存在一个数可以被其他所有数都整除,那么其实就是要满足条件:Min(a[i])【L<=i<=R】==Gcd【L,R】;


2、我们还知道,假设我们有一个起点i,那么我们不断的增大区间右端点的位子的话,会使得Gcd逐渐变小,min值也逐渐变小。

这里肯定是存在一个单调性的,但是我们O(n)枚举起点然后二分终点的话,我们要确定下来一个最小值的问题,而且两个值都在变小,我们相对check起来有点难。

那么我们不妨换个角度去做。


我们O(n)枚举一个位子i,使得这个位子i作为区间中的最小值,那么我们向左二分最远点,使得【L,i】的最小值等于a【i】,然后向右二分一个最远点,使得【i,R】的最小值也等于a【i】,那么我们现在就通过这个方式取得了一个区间【L,R】是以a【i】作为最小值的一个区间。

我们知道,如果以这个位子i作为中心点向左扩大区间的话,其Gcd的值要么会下降,要么会保持不变,其也包含一个单调性

所以我们接下来再以i作为中心点,

向左二分找到最远点,使得Gcd【ansL,i】是等于a【i】的,

再向右二分找到最远点,使得Gcd【i,ansR】也是等于a【i】的。

那么对于a【i】作为中心点的最优解就是【ansL,ansR】了,过程维护最优解即可。


3、一开始用线段树跑的区间最小值和Gcd值,TLE了,O(n*logn*longn),n相对有些大 。

这个题是静态不会改变A【i】数组的一个题,所以我们不妨维护一个ST表,就能做到O(1)查询,所以用ST表求Gcd和最小值的时间复杂度为O(nlogn);


Ac代码:

#include<stdio.h>
#include<string.h>
#include<vector>
#include<math.h>
#include<map>
using namespace std;
#define lson l,m,rt*2
#define rson m+1,r,rt*2+1
int flag,cnt,maxn;
vector<int >ans;
map<int,int>s;
int a[1221212];
int minn[400005][20];
int Gcd[400005][20];
int n,q;
int gcd(int x,int y)
{
    return y==0?x:gcd(y,x%y);
}
void ST()
{
    int len=floor(log10(double(n))/log10(double(2)));
    for(int j=1;j<=len;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]);
            Gcd[i][j]=gcd(Gcd[i][j-1],Gcd[i+(1<<(j-1))][j-1]);
        }
    }
}
int getminn(int a,int b)
{
    int len= floor(log10(double(b-a+1))/log10(double(2)));
    return min(minn[a][len], minn[b-(1<<len)+1][len]);
}
int getGcd(int a,int b)
{
    int len= floor(log10(double(b-a+1))/log10(double(2)));
    return gcd(Gcd[a][len],Gcd[b-(1<<len)+1][len]);
}
void Slove(int L,int R,int i)
{
    int ansL=-1;
    int l=L;
    int r=i;
    while(r-l>=0)
    {
        int mid=(l+r)/2;
        if(getGcd(mid,i)==a[i])
        {
            ansL=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    if(ansL==-1)return ;
    int ansR=-1;
    l=i;
    r=R;
    while(r-l>=0)
    {
        int mid=(l+r)/2;
        if(getGcd(i,mid)==a[i])
        {
            l=mid+1;
            ansR=mid;
        }
        else r=mid-1;
    }
    if(ansR==-1||ansL==-1)return ;
    if(ansR-ansL>maxn)
    {
        s.clear();
        ans.clear();
        maxn=ansR-ansL;
    }
    if(ansR-ansL==maxn)
    {
        if(s[ansL]==0)
        ans.push_back(ansL),s[ansL]=1;
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        maxn=-1;
        flag=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            minn[i][0]=a[i];
            Gcd[i][0]=a[i];
            if(a[i]==1)flag=1;
        }
        if(flag==1)
        {
            printf("1 %d\n",n-1);
            printf("1\n");
            continue;
        }
        ST();
        for(int i=1;i<=n;i++)
        {
            int L=-1;
            int l=1;
            int r=i;
            while(r-l>=0)
            {
                int mid=(l+r)/2;
                if(getminn(mid,i)==a[i])
                {
                    L=mid;
                    r=mid-1;
                }
                else l=mid+1;
            }
            int R=-1;
            l=i;
            r=n;
            while(r-l>=0)
            {
                int mid=(l+r)/2;
                if(getminn(i,mid)==a[i])
                {
                    R=mid;
                    l=mid+1;
                }
                else r=mid-1;
            }
            Slove(L,R,i);
        }
        printf("%d %d\n",ans.size(),maxn);
        for(int i=0;i<ans.size();i++)
        {
            printf("%d ",ans[i]);
        }
        printf("\n");
    }
}















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值