二分+ST表
https://www.luogu.com.cn/problem/solution/P7009
题意:给定一个序列,有 组询问,每次给出 个数 。
你需要找到这个数组的一个子序列(要求编号连续),使得该序列中所有数的最大公约数和序列长度的乘积最大,并输出这个最大值。
首先, 满足可重复合并性质,可以 表做到不修改的 查询。
然后,前缀 有单调不增的性质,并且有:
对于一定的 的值,肯定是区间长度越大越好,那么我们可以二分查找最右边的端点并更新答案。
因此我们可以枚举左端点,第一次查找 就是其本身的,然后每次二分更新答案,并且将寻找的 改成第一个不满足的 值。
时间复杂度,。
#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;
}
注意:这个 一定不能清到 ,否则重复寻找而且慢,还容易挂。
https://codeforces.com/contest/475/problem/D
题意:
给出一个长度为 的序列和 个询问,每个询问输出一行,询问 的 的对数.
思路是刚才的变形。只需要预处理即可,一共不会超过 个 的值。
#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;
}
区间 同样可以使用 ST 表维护。
比如下面这一题:
https://www.luogu.com.cn/problem/CF689D
题意:
- 给定序列 和序列 ,长度均为 。问有多少组 ,满足 且
- ,。
注意到,左端点固定的情况下, 单调不降, 单调不升,于是如果存在 使得满足题意,那么在 一定是 ,在 一定是 。于是满足题意的 一定是一段数字,分别二分查找左右端点即可。
#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
题意:
有一个长度为 的正整数数列 ,现在想找到这个数列中最长的一个区间,满足区间中有一个数 可以整除区间中任意数。
第一行输出两个正整数 ,表示满足要求的最长区间的个数与长度。
第二行输出 个升序排列的正整数,表示所有满足要求的最长区间的左端点。
这里,区间的长度定义为右端点减左端点。
只需要判断区间 是不是等于区间 即可。(这个数 一定是最小值)
和 都是单减,没有单调性。从哪儿入手呢?
如果一个区间满足题意,那么它包含 的那个子区间一定也满足题意,比如:
满足题意,那么 一定满足题意。
因此区间的长度有单调性。
我们可以二分区间长度,然后对于每一个长度, 枚举左端点,并判断是否存在一个 满足 即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效