题解 CF359D

Posted s-t-a-r-d-u-s-t

tags:

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

$by$ ???????????????? $for$ ?

(CF359D;Pair;of;Numbers)

https://www.luogu.org/problem/CF359D

https://codeforces.com/problemset/problem/359/D

题目描述

给出一个长度为(n)的数列,求最长的区间使区间中存在一个数(x)整除区间所有数。

输入格式

输出格式

第一行输出两个正整数(cnt)(len)表示满足要求的最长区间的个数与长度

第二行输出(cnt)个升序排列的正整数,表示所有满足要求的最长区间的左端点

数据范围分析

(nle3 imes10^{5};)

(1le a_{i}le 10^{6};)

(a_{i})不重要,由(n)的范围可看出(n^2)是不行的,可能是(n)(nlogn)

当然在此基础上可以再加个(log),事实上正解就是(nlog^2n)的。

(code)

#include<bits/stdc++.h>
int n,m,ans,a[300001],g[1200001],min[1200001],last[1200001],book[1200001];
inline int gcd(int x,int y)
{
    return y?gcd(y,x%y):x;
}

inline void build_gcd(int k,int l,int r)
{
    if(l==r)
    {
        g[k]=a[l];
        return;
    }
    
    build_gcd(k<<1,l,l+r>>1);
    build_gcd(k<<1|1,(l+r>>1)+1,r);
    g[k]=gcd(g[k<<1],g[k<<1|1]);
    return;
}

inline int query_gcd(int k,int l,int r,int x,int y)
{
    if(r<x||l>y)
        return 0;
    if(l>=x&&r<=y)
        return g[k];
    return gcd(query_gcd(k<<1,l,l+r>>1,x,y),query_gcd(k<<1|1,(l+r>>1)+1,r,x,y));
}

inline void build_min(int k,int l,int r)
{
    if(l==r)
    {
        min[k]=a[l];
        return;
    }
    
    build_min(k<<1,l,l+r>>1);
    build_min(k<<1|1,(l+r>>1)+1,r);
    min[k]=std::min(min[k<<1],min[k<<1|1]);
    return;
}

inline int query_min(int k,int l,int r,int x,int y)
{
    if(r<x||l>y)
        return 0x7fffffff;
    if(l>=x&&r<=y)
        return min[k];
    return std::min(query_min(k<<1,l,l+r>>1,x,y),query_min(k<<1|1,(l+r>>1)+1,r,x,y));
}

inline int check(int len)
{
    int res=0;
    memset(book,0,sizeof(book));
    for(register int i=1;i<=n-len;i=-~i)
        if(!(query_min(1,1,n,i,i+len)^query_gcd(1,1,n,i,i+len)))
            book[++res]=i;
    return res;
}

int main()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;i=-~i)
        scanf("%d",&a[i]);
    build_gcd(1,1,n);
    build_min(1,1,n);
    int l=0,r=n-1;
    while(l<=r)
    {
        int mid=l+r>>1;
        int c=check(mid);
        if(c)
        {
            ans=c;
            l=-~mid;
            for(register int i=1;i<=ans;i=-~i)
                last[i]=book[i];
        }
        
        else
            r=mid-1;
    }
    
    printf("%d %d
",ans,l-1);
    for(register int i=1;i<=ans;i=-~i)
        printf("%d ",last[i]);
    return 0;
}

(solution)

区间问题,可以想到用线段树

如何判断整除?既然(x)可以整除区间中所有数,则(x)就是本区间的公约数。又因为(x)本身也属于这个区间,所以(x)就是区间(gcd)。显然这个东西可以通过分治到子区间解决。

因为(x)整除区间所有数,又可以发现它就是区间最小值。于是建两棵树。

枚举所有区间,找到(x)和区间(gcd),判断是否相等,计算最大答案。复杂度(O(n^2logn)Longleftrightarrow O(跑不过))

线段树这边很难优化了。只能优化查找区间操作。

发现显然答案具有单调性:若某个长区间满足,则它包含的短区间必然满足。故使用二分答案

建树后二分区间长度,枚举区间左端点,对每个区间计算(x)(gcd),判断是否满足。(O(nlog^2n)),跑得过。

没有修改操作,实现不难。

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

题解 CF1063B Labyrinth

CF Round #631 题解

CF886E 题解

CF524B 题解

题解 CF1216D Swords

题解 CF1354D Multiset