CF475D CGCDSSQ(二分+ST表)

题目

题目传送门
给出一个长度为 n ( 1 &lt; = n &lt; = 1 0 5 ) n(1&lt;=n&lt;=10^{5}) n(1<=n<=105)的序列和 q ( 1 &lt; = q &lt; = 3 ∗ 1 0 5 ) q(1&lt;=q&lt;=3*10^{5}) q(1<=q<=3105)个询问,每个询问输出一行,询问 g c d ( a l , a l + 1 , ⋯ &ThinSpace; , a r ) = x gcd(a_l,a_{l+1},\cdots,a_r)=x gcd(al,al+1,,ar)=x ( i , j ) (i,j) (i,j)的对数.

题解

  • 大佬给我们讲分治时用的例题,我这种不会分治的cb赶紧去学了一下,发现这道题真的非常妙 q w q qwq qwq
  • 首先如果我们固定一个左端点,那么在右端点移动的过程中 gcd ⁡ \gcd gcd一定是单调不增的,而如果 gcd ⁡ \gcd gcd出现变化时,它一定至少变了 2 2 2,所以 gcd ⁡ \gcd gcd的变化是 log ⁡ \log log级别的,那么这样我们就可以预处理出每个区间的 gcd ⁡ \gcd gcdST表好啊蒟蒻的ST表讲解qwq
  • 预处理出每个 gcd ⁡ \gcd gcd之后,我们就可以以 1 1 1~ n n n的每个点为左端点,二分查找最短的与左端点 gcd ⁡ \gcd gcd相同的右端点,用 m a p map map记录此段区间个数,一直处理到 R = n R=n R=n;这样我们就能得出题目要求的区间了。

c o d e code code

#include <bits/stdc++.h> 
using namespace std; 
#define maxn 101000
typedef long long LL; 

template <typename T> 
inline void read(T &s) {
	s = 0; 
	T w = 1, ch = getchar(); 
	while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
	while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
	s *= w; 
}

int n, m; 
int a[maxn], lg[maxn], q[maxn]; 
int f[maxn][25]; 
map <int, LL> ans; 

inline int gcd(int x, int y) { return y ? gcd(y, x % y) : x; } 

inline void pre_work() { // 预处理gcd
	lg[0] = -1; 
	for (int i = 1; i <= n; ++i) {
		lg[i] = lg[i >> 1] + 1, f[i][0] = a[i]; 
	}
	for (int j = 1; j <= 20; ++j) {
		for (int i = 1; i <= n - (1 << (j - 1)) + 1; ++i) {
			f[i][j] = gcd(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); 
		}
	}
}

inline int query(int l, int r) { // 查询
	int t = lg[r - l + 1]; 
	return gcd(f[l][t], f[r - (1 << t) + 1][t]); 
}

void solve(int x) { // x 为固定的左端点
	int L = x, R = x; 
	while (R <= n) {
		int left = L, right = n; 
		int g = query(x, L); 
		while (left <= right) {
			int mid = (left + right) >> 1; 
			if (query(x, mid) == g) left = mid + 1; 
			else right = mid - 1; 
		}
		R = left; 
		ans[g] += (LL)R - L; 
		L = R; 
	}
} 

int main() {
	read(n); 
	for (int i = 1; i <= n; ++i) read(a[i]); 
	
	pre_work(); 
	
	for (int i = 1; i <= n; ++i) solve(i); // 处理左端点

	read(m); 
	for (int i = 1; i <= m; ++i) {
		int x; read(x); 
		printf("%lld\n", ans[x]); 
	}
	return 0; 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值