W
e
l
c
o
m
e
: )

[ARC023D] GCD区間 题解

[ARC023D] GCD区間

upd on 2024.1.26:修正了 ST 表的时间复杂度分析。

一道数据结构维护 \(\gcd\) 的好题。


首先,大家应该都能想到一种大暴力:枚举左右端点 \(l, r\), 暴力计算 \(\gcd\) 然后开个哈希表记录每个 \(\gcd\) 出现了几次。

复杂度 \(O(n^2\log{n})\) 预处理,\(O(1)\) 查询。


很明显,\(O(n^2\log{n})\) 压根卡不过去。

但是这道题涉及到了区间求 \(\gcd\),可以考虑用数据结构维护。

所以我们简单用一个 ST 表来维护 \(\gcd\),成功做到 \(O(n^2)\) 预处理,\(O(\log{n})\) 查询。(ST 表中 \(\gcd\) 操作的复杂度为 \(O(\log{n})\)

template<typename Tp, size_t siz>
struct ST
{
    Tp _con[siz][21];   
  
    Tp& operator[](int i) {return _con[i][0];}

    void pre()
    {
        for(int i=1;i<=20;i++)
            for(int j=1;j+(1<<i)-1<=siz;j++)
                _con[j][i]=gcd(_con[j][i-1], _con[j+(1<<(i-1))][i-1]);
    }

    Tp query(int l, int r)
    {
        if(l>r) return 0;
        int len=lgs[r-l+1];
        return gcd(_con[l][len], _con[r-(1<<len)+1][len]);
    }
};

但是还是卡不过去啊

考虑这样一个序列:12 8 4 6 4 3

\(l=1\) 时,区间 \([l,r]\) 内的 \(\gcd\) 为( \(r\)\(1\)\(n\) ):12 4 4 2 2 1

我们发现该题的 \(\gcd\) 有一条重要性质:

  • \(l\) 一定时,\(\gcd\) 单调递减。

所以我们想到二分答案。

二分一段相同 \(\gcd\) 的右端点,直接统计答案。
时间复杂度 \(O(n\log{n})\)

Code

#include<bits/stdc++.h>
#include<bits/extc++.h>
#define pb __gnu_pbds
using namespace std;

template< typename T >inline void read(T &x)
{
	char c=getchar();x=0;int f=0;
	for(;!isdigit(c);c=getchar()) f|=(c=='-');
	for(;isdigit(c);c=getchar()) x=((x<<3)+(x<<1)+(c^48));
	x=f?-x:x;
}

template< typename T >inline void write(T x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10^48);
}

template< typename T,typename ... Args > 
inline void read(T &_x, Args &... args) 
{read(_x), read(args...);}

template< typename T,typename ... Args > 
inline void write(T _x, Args ... args) 
{ write(_x), write(args...);}

//快读快写

pb::gp_hash_table<int,long long> gcdct; //用一个哈希表存储 gcd 为某值的对数

inline int gcd(int a,int b)
{
	if(!(a&&b)) return a|b;
	int alow=__builtin_ctzll(a), blow=__builtin_ctzll(b);
	int low=(alow<blow)?alow:blow;
	int c=0;
	b>>=blow;
	while(a)
    {
		a>>=alow;
		c=b-a;
		alow=__builtin_ctzll(c);
		if(a<b) b=a;
		a=(c<0)?-c:c;
	}
	return b<<low;
} // Stein gcd 更相减损法求 gcd

int lgs[100005];
void prel()
{
    for(int i=2;i<=100000;i++) lgs[i]=lgs[(i>>1)]+1;
} // 预处理 log2(n) 

template<typename Tp, size_t siz>
struct ST
{
    Tp _con[siz][21];   
  
    Tp& operator[](int i) {return _con[i][0];}

    void pre()
    {
        for(int i=1;i<=20;i++)
            for(int j=1;j+(1<<i)-1<=siz;j++)
                _con[j][i]=gcd(_con[j][i-1], _con[j+(1<<(i-1))][i-1]);
    }

    Tp query(int l, int r)
    {
        if(l>r) return 0;
        int len=lgs[r-l+1];
        return gcd(_con[l][len], _con[r-(1<<len)+1][len]);
    }
};

ST<int, 100005> lis; // 相当简单的 ST 表,维护区间 gcd

inline int find(int l, int r0, int n)
{
    int g0=lis.query(l, r0);
    int ret=0, r=n;
    while(r0<=r)
    {
        int mid=(r0+r)>>1;
        if(lis.query(l, mid)==g0) r0=mid+1, ret=mid;
        else r=mid-1;
    }
    return ret;
} // 二分查找右端点

signed main()
{
    int n,q;
    read(n, q);
    prel();
    for(int i=1;i<=n;i++) read(lis[i]);
    lis.pre(); // 读入和预处理部分
    for(int l=1;l<=n;l++)
    {
        int ls, nr=l;
        for(;nr<=n;)
        {
            ls=nr;
            nr=find(l, nr, n);
            gcdct[lis.query(l, ls)]+=nr-ls+1;
            nr++;
        } // 枚举左端点,统计答案
    }
    while (q--)
    {
        read(n);
        write(gcdct[n]); putchar(10); // O(1)查询
    }
}
posted @ 2024-08-25 20:53  Jimmy-LEEE  阅读(36)  评论(0)    收藏  举报