Codeforces1549D Integers Have Friends (思维 线段树)

99 篇文章 2 订阅
69 篇文章 6 订阅

题目链接: Integers Have Friends

大致题意

给出长度为n的序列a.

如果存在整数m ≥ 2, a[l] % m = a[l + 1] % m = … = a[r] % m. 则称该连续子序列a[l, r]是友好的.

问: 最长的连续友好子序列长度是多少.

解题思路

思维

考虑到对于两个数x和y而言, 如果x % m == y % m, 则|x - y|一定是m的倍数.
扩展: 那么对于若干个数字, 如果满足numi % m == C, 则任意两个数字之间的差值也一定是m的倍数.

此时需要注意: 由于m ≥ 2, 因此如果|x - y| == 1, 则我们是无法找到一个合法的m的.

我们可以得出结论: 对于若干个数字而言, m = gcd({ |任两个数字之间的差值| }).


但是这种判断方式是O(n2)的, 我们考虑如何优化:

其实我们每次只需要计算相邻两个数字之间的差值即可.

如果a[i]与a[j]的差值是m的倍数, a[j]与a[k]的差值也是m的倍数, 则a[i]与a[k]的差值也一定为m的倍数.


对于本题而言, 我们可以记序列b, 其中b[i] = abs(a[i] - a[i - 1]).

如果想判断是否存在m, 使得[l, r]友好, 我们只需要判断gcd(b[l + 1, r])是否不等于1即可.


线段树

通过上述分析发现我们需要维护区间gcd信息, 我们可以采用线段树来维护.
(由于是静态查询, 我们也可以用st表).

这样我们可以通过枚举区间右端点r的方式, 二分来找到符合条件的l.

但实际上, 假设上次合法的区间为[L, R], 我们下一次该枚举到R + 1了, 左指针的移动实际上并不会向左移动. 因为[L - 1, R]不合法, 则[L - 1, R + 1]也必然不合法.

因此我们发现左指针的移动是单调的, 可以用双指针的思路来进行优化.

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10;
ll a[N], b[N];
struct node {
	int l, r;
	ll val;
}t[N << 2];
void pushup(int x) { t[x].val = gcd(t[x << 1].val, t[x << 1 | 1].val); }
void build(int l, int r, int x = 1) {
	t[x] = { l, r, b[l] };
	if (l == r) return;
	int mid = l + r >> 1;
	build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
	pushup(x);
}
ll ask(int l, int r, int x = 1) {
	if (l <= t[x].l and r >= t[x].r) return t[x].val;
	int mid = t[x].l + t[x].r >> 1;
	ll res = 0;
	if (l <= mid) res = ask(l, r, x << 1);
	if (r > mid) res = gcd(res, ask(l, r, x << 1 | 1));
	return res;
}
int main()
{
	int T; cin >> T;
	while (T--) {
		int n; scanf("%d", &n);
		rep(i, n) scanf("%lld", &a[i]);

		if (n == 1) { printf("1\n"); continue; } // 特判

		for (int i = 2; i <= n; ++i) {
			b[i] = abs(a[i] - a[i - 1]);
		}
		build(2, n);

		int res = 1, l = 2;
		for (int r = 2; r <= n; ++r) {
			if (b[r] == 1) continue;

			while (l < r) {
				if (ask(l, r) == 1) l++;
				else break;
			}
			res = max(res, r - l + 2);
		}

		printf("%d\n", res);
	}

	return 0;
}

END

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值