Codeforces1547F Array Stabilization (GCD version) (思维 线段树)

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

题目链接: Array Stabilization (GCD version)

大致题意

给定一个长度为n的数组, 下标从1~n. 其中an和a1相连(成环).

每轮操作得到一个新的数组b: 对于所有的i∈[1, n], b[i] = gcd(a[i], a[i + 1]) (b[n] = gcd(a[n], a[1])). 最后把新数组b复制给原数组a.

问: 执行完多少轮操作后, a数组中的所有数字都相同.

解题思路

考虑到最终a数组, 会有很多种相等的可能情况. 这不便于我们接下来的思考.

两个数取gcd, 相当于保留两个数的公共质因子. 对于整个序列执行无数次gcd操作后, 等价于序列中每个数字变为原序列中所有公共质因子的乘积 . 因此我们不妨执行 a[] /= gcd(a[]). 此时我们最终得到的序列一定是全1序列.


现在我们的目的就很明确了: 求执行完多少次操作后, 使得序列中每一个数字都等于1.


考虑到是每次是i位置的数字(假设a[i] != 1), 与i+1位置的数字进行gcd操作, 如果两个数字本身互质, 则操作1次就可以使得i位置数字变为1.

若不互质, 不妨假设存在公因子2, 则此轮变化后, a[i] = 2, a[i+1]我们需要根据a[i+2]来确定, 而a[i+2]的情况需要根据…… 我们考虑对a[i+1]一轮操作后的结果分情况考虑:
      情况①: 如果操作后a[i+1]中不含有因子2, 则表明a[i+2]中也不含有因子2, 此时操作两轮可以把a[i]变为1.
      情况②: 如果操作后a[i+1]中存在因子2, 则表明a[i+2]中也存在因子2, 此时我们又需要假设a[i+2]的情况, 而情况情况是同于假设a[i+1]的.

我们观察可以发现, 如果a[i], a[i + 1], …, a[i + k]都存在公因子2, a[k + 1]不存在因子2, 则我们需要k轮操作, 才能把a[i]变为1.


得出结论: 对于每一个位置i, 我们开始连续取gcd, 当第一次出现 gcd(a[i], a[i + 1], …, a[i + k]) == 1 时, 表示i位置需要操作k轮才能变为1.

那么对于本题, 我们把所有位置的k取max即为最终答案.


我们怎么得到每个位置的k呢? 线段树 + 树外二分 DS选手只能想到暴力线段树

我们用线段树维护每个区间静态gcd的信息. 对于每个位置查询最靠左gcd == 1的位置即可.


这里还有一点点细节问题: 由于a[n]后面是a[1], 题目中的数组是个, 我们可以通过把数组复制一遍的方式来模拟环. 即: a[n + 1] = a[1], a[n + 2] = a[2], …, a[n + n] = a[n].

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 = 4E5 + 10; //记得开2倍
int a[N];
struct node {
	int l, r;
	int 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, a[l] };
	if (l == r) return;
	int mid = l + r >> 1;
	build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
	pushup(x);
}
int ask(int l, int r, int x = 1) { //查询[l, r]的gcd
	if (l <= t[x].l and r >= t[x].r) return t[x].val;
	int mid = t[x].l + t[x].r >> 1;
	int 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);

		int d = 0; //得到gcd(a[]).
		rep(i, n) scanf("%d", &a[i]), d = gcd(d, a[i]);
		if (d != 1) rep(i, n) a[i] /= d;

		rep(i, n) a[n + i] = a[i];
		n <<= 1;

		build(1, n);

		int res = 0;
		rep(i, n / 2) {
			if (a[i] == 1) continue;

			int l = i + 1, r = n; //右端点的取值区间
			while (l < r) {
				int mid = l + r >> 1;
				int cou = ask(i, mid);
				if (cou == 1) r = mid;
				else l = mid + 1;
			}
			res = max(res, r - i);
		}

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

	return 0;
}

END

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

逍遥Fau

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

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

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

打赏作者

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

抵扣说明:

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

余额充值