[ARC023D] GCD区間 题解

[ARC023D] GCD区間

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

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


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

复杂度 O(n2logn) 预处理,O(1) 查询。


很明显,O(n2logn) 压根卡不过去。

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

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

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 为( r1n ):12 4 4 2 2 1

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

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

所以我们想到二分答案。

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

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)查询
}
}

本文作者:Jimmy-LEEE

本文链接:https://www.cnblogs.com/redacted-area/p/18379516

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Jimmy-LEEE  阅读(6)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起