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

题解 CF359D

时间:2019-10-30 22:35:49      阅读:99      评论:0      收藏:0      [点我收藏+]

标签:实现   its   span   分治   code   ext   register   ret   size   

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

\(CF359D\;Pair\;of\;Numbers\)

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

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

题目描述

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

输入格式

输出格式

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

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

数据范围分析

\(n\le3\times10^{5};\)

\(1\le 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\n",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

标签:实现   its   span   分治   code   ext   register   ret   size   

原文地址:https://www.cnblogs.com/s-t-a-r-d-u-s-t/p/11768207.html

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