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

浙公网安备 33010602011771号