二分+ST表

https://www.luogu.com.cn/problem/solution/P7009

题意:给定一个序列,有 T 组询问,每次给出 n 个数 ai

你需要找到这个数组的一个子序列(要求编号连续),使得该序列中所有数的最大公约数和序列长度的乘积最大,并输出这个最大值。

首先,gcd 满足可重复合并性质,可以 ST 表做到不修改的 O(1) 查询。

然后,前缀 gcd 有单调不增的性质,并且有:
 n  gcd  logn 
对于一定的 gcd 的值,肯定是区间长度越大越好,那么我们可以二分查找最右边的端点并更新答案。

因此我们可以枚举左端点,第一次查找 gcd 就是其本身的,然后每次二分更新答案,并且将寻找的 gcd 改成第一个不满足的 gcd 值。

时间复杂度,O(n)×O(logn)×O(logn)

#include<bits/stdc++.h>
using namespace std;
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int n;
ll a[100010];
ll st[100010][25];
ll query(int x, int y) {
    int xx = log2(y - x + 1);
	return __gcd(st[x][xx], st[y - (1 << xx) + 1][xx]);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    int t; cin >> t;
    while(t--) {
    	cin >> n;
    	ll ans = 0;
    	f(i, 1, n) {
    		cin >> a[i];
    		st[i][0] = a[i];
		}
		f(i, 1, 20) {
			f(j, 1, n) {
				int len = (1 << i);
				if(j + len - 1 > n) break;
				st[j][i] = __gcd(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
			}
		}
		f(l, 1, n) {
		    ll gcd = a[l], bound = query(l, n), rl = l;
			while(gcd > bound) {
				int rr = n;
				while(rl < rr) {
					int mid = (rl + rr + 1) >> 1;
					if(query(l, mid) == gcd) rl = mid;
					else rr = mid - 1;
				}
				ans = max(ans, gcd * (rl - l + 1));
				gcd = query(l, rl + 1);
				rl = rl + 1;
			}
			ans = max(ans, bound * (n - l + 1));	
		}
		cout << ans << endl;
	}
    return 0;
}

注意:这个 rl 一定不能清到 l,否则重复寻找而且慢,还容易挂。

https://codeforces.com/contest/475/problem/D

题意:
给出一个长度为n(1<=n<=105) 的序列和q(1<=q<=3105) 个询问,每个询问输出一行,询问gcd(al,al+1,...,ar)=x(i,j) 的对数.

思路是刚才的变形。只需要预处理即可,一共不会超过 nlogngcd 的值。

#include<bits/stdc++.h>
using namespace std;
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int n;
ll a[100010];
ll st[100010][25];
ll query(int x, int y) {
    int xx = log2(y - x + 1);
	return __gcd(st[x][xx], st[y - (1 << xx) + 1][xx]);
}
map<ll, ll> num;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    cin >> n;
    
    f(i, 1, n) {
    	cin >> a[i];
    	st[i][0] = a[i];
	}
	f(i, 1, 20) {
		f(j, 1, n) {
			int len = (1 << i);
			if(j + len - 1 > n) break;
			st[j][i] = __gcd(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
		}
	}
	f(l, 1, n) {
		ll gcd = a[l], bound = query(l, n), lastrl = l, rl = l;
		while(gcd > bound) {
			int rr = n;
			while(rl < rr) {
				int mid = (rl + rr + 1) >> 1;
				if(query(l, mid) == gcd) rl = mid;
				else rr = mid - 1;
			}
			if(!num.count(gcd)) num[gcd] = 0;
			num[gcd] += (rl - lastrl + 1);
			gcd = query(l, rl + 1);
			rl = rl + 1; lastrl = rl;
		}
		if(!num.count(bound)) num[bound] = 0;
		num[bound] += (n - lastrl + 1);
	}
	int q; cin >> q;
	while(q--) {
		ll x; cin >> x;
		if(!num.count(x)) cout << 0 << endl;
		else cout << num[x] << endl;
	}
    return 0;
}

区间 min,max 同样可以使用 ST 表维护。
比如下面这一题:
https://www.luogu.com.cn/problem/CF689D

题意:

  • 给定序列 a 和序列 b,长度均为 n。问有多少组 (l,r),满足 1lrn

maxi=lrai=mini=lrbi

  • 1n2×105|ai|,|bi|109

注意到,左端点固定的情况下,max 单调不降,min 单调不升,于是如果存在 r 使得满足题意,那么在 l r1 一定是 min>=max,在 r+1,n 一定是 max>=min。于是满足题意的 r 一定是一段数字,分别二分查找左右端点即可。

#include<bits/stdc++.h>
using namespace std;
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int n;
ll a[200010], b[200010];
ll mn[200010][25], mx[200010][25];
ll qmn(int x, int y) {
    int xx = log2(y - x + 1);
	return min(mn[x][xx], mn[y - (1 << xx) + 1][xx]);
}
ll qmx(int x, int y) {
    int xx = log2(y - x + 1);
	return max(mx[x][xx], mx[y - (1 << xx) + 1][xx]);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    cin >> n;
    f(i, 1, n) {
    	cin >> a[i];
    	mx[i][0] = a[i];
	}
    f(i, 1, n) {
    	cin >> b[i];
    	mn[i][0] = b[i];
	}	
	f(i, 1, 20) {
		f(j, 1, n) {
			int len = (1 << i);
			if(j + len - 1 > n) break;
			mn[j][i] = min(mn[j][i - 1], mn[j + (1 << (i - 1))][i - 1]);
			mx[j][i] = max(mx[j][i - 1], mx[j + (1 << (i - 1))][i - 1]);
		}
	}
	ll ans = 0;
	f(i, 1, n) {
		if(a[i] > b[i]) continue;
		if(qmx(i, n) < qmn(i, n)) continue;
		int ll = i, lr = n;
		while(ll < lr)
		{
			int mid = (ll + lr) >> 1;
		    if(qmx(i, mid) < qmn(i, mid)) ll = mid + 1;
		    else lr = mid;
		}
		int rl = i, rr = n;
		while(rl < rr) {
			int mid = (rl + rr + 1) >> 1;
		    if(qmx(i, mid) > qmn(i, mid)) rr = mid - 1;
		    else rl = mid;
		}		
		if(ll > rl) continue;
		else ans += rl - ll + 1;
	}
	cout << ans << endl;
    return 0;
}

https://www.luogu.com.cn/problem/CF359D

题意:
Simon 有一个长度为 N 的正整数数列 a1,a2,,an ,现在想找到这个数列中最长的一个区间,满足区间中有一个数 x 可以整除区间中任意数。
第一行输出两个正整数 cnt , len,表示满足要求的最长区间的个数与长度。
第二行输出 cnt 个升序排列的正整数,表示所有满足要求的最长区间的左端点。
这里,区间的长度定义为右端点减左端点。

只需要判断区间 gcd 是不是等于区间 min 即可。(这个数 x 一定是最小值)
gcdmin 都是单减,没有单调性。从哪儿入手呢?
如果一个区间满足题意,那么它包含 min 的那个子区间一定也满足题意,比如:
693 满足题意,那么 93 一定满足题意。
因此区间的长度有单调性。
我们可以二分区间长度,然后对于每一个长度, O(n) 枚举左端点,并判断是否存在一个 l,r 满足 gcd(l,r)==min(l,r) 即可。

posted @   OIer某罗  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示