AcWing 246. 区间最大公约数

传送门

由于在此题解中加入了许多自己对细节的处理,所以长文警告(雾)

Description

给定一个长度为 \(N\) 的数列 \(A\),以及 \(M\) 条指令,每条指令可能是以下两种之一:

C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 \(d\)
Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数 (GCD)。
对于每个询问,输出一个整数表示答案。

Constraints

\(N≤500000,M≤100000,1≤A[i]≤10^{18},|d|≤10^{18}\)

Solution

Part 1 主要思路

我们首先的思路是考虑能不能直接去维护两种操作,很可惜这题不行。

由于这题在求 gcd 同时还会区间加,而可能 \(gcd(a, b)≠gcd(a+x, b+x)\),不满足子区间可合并性,所以不能直接去维护。

除了辗转相除法求 gcd 以外,我们还能再想到效率稍低的更相减损术(证就不证了)

\(gcd(a, b)=gcd(a, b-a)\)

此式子可以推广到多个数,由此可推广到本题,设查询这个区间的左右端点分别为 \(l\)\(r\),则有:

\(\qquad \qquad \qquad \qquad gcd(a[l], a[l+1], a[l+2],..., a[r]) = gcd(a[l], a[l+1] - a[l], a[l + 2] - a[l+1],...,a[r]-a[r-1])\)

因此,我们所查询的左式可以转化为右式。

观察右式,可以发现除第一项 \(a[l]\) 之外,后面的就是原数组 \(a\) 的差分数组!

\(a\) 的差分数组为 \(b\),则

\(\qquad \qquad \qquad \qquad gcd(a[l], a[l+1] - a[l], a[l + 2] - a[l+1],...,a[r]-a[r-1])=gcd(a[l],b[l+1],b[l+2],...,b[r])\)

而去维护差分数组需要维护单点修改与区间 gcd,可以使用线段树或树状数组维护。

同时,每次查询的第一项 \(a[l]\) 是原数组,需要再建立一棵线段树,去维护原数组的区间修改与单点查询。

总结:维护两棵线段树,一棵是原数列,区间修改+单点查询(这么做会TLE,仍需经过处理,在Part2中会讲到),一棵是原序列的差分序列,单点修改+区间查询。

Part 2 一些代码实现的细节

这题我调了6h+,WA了十几发才过,发现了里面一些需要注意的东西(虽然可能大家都知道)

如果您是口胡人当我没说

+ 差分数组中 gcd 会出现负数与 \(0\) 情况

由于 gcd 不能出现负数,又根据 \(gcd(a,b)=gcd(a,-b)\) 可知,直接在手写 gcd 函数注意取绝对值即可。

但是不能直接为差分数组取绝对值!差分数组能反映原数组相邻数之间的增减情况的,不能更改!

同时,差分数组也会有 \(0\),而在 gcd 中 \(mod\)\(0\) 就爆炸了,根据 \(gcd(a,0)=a\) ,对 \(0\) 情况特殊处理一下。

+ 差分数组越界情况

由于差分数组处理是下方这样:

a[l] += x;  a[r + 1] -= x;

所以可能导致\(r+1\)下表超出线段树 \(n\) 的范围导致蜜汁 \(MLE\),而 \(r+1\) 位置数值的变化我们并不关心,若 \(r+1 \ge n\) 直接特判不进行操作即可。

在最后输出答案时也要小心,因为也有可能越界!

+ 维护原数组中 \(pushdown\) 下传操作爆 \(long\) \(long\) 的问题

区间最大长度高达 \(10^6\),在下传中乘一下可能 \(10^{18}\) 大小的 \(d\),开 \(long\) \(long\) 也救不回来 /youl/youl/youl

高精度的话复杂度不够,只能想办法避免 \(pushdown\) 操作。

在学习线段数组时,也使用了差分来将区间修改变成了单点修改,在此题中也可以应用。

将维护原数组的线段树也改变成维护差分数组,只不过这次维护的是区间和。

式子又可以变成

\(\qquad \qquad \qquad \qquad gcd(a[l],b[l+1],b[l+2],...,b[r]) = gcd((b[1]+b[2]+...+b[l]),b[l+1],b[l+2],...,b[r])\)

这次都可以正确地维护了。

Code

这是原来的两棵线段树修改而成的,为直观理解,未对两棵线段树的相同操作与函数进行合并。(所以会有点长)

读者在自行写代码时可以把一些函数进行合并,节省码量。

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define int long long
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 500100;
int n, m;
int a[N], b[N], t[N];
int f1[N * 4], f2[N * 4];//f1 数组表示差分求和,f2 数组表示差分gcd
int gcd(int xx, int yy)
{   
        xx = abs(xx);//细节:gcd里不要出现负数
        yy = abs(yy);
	if(yy == 0)//细节:gcd 中出现 0 时特判
	{
		return xx;
	}
	if(xx % yy == 0)
	{
		return yy;
	}
	else
	{
		return gcd(yy, xx % yy);
	}
}
void pushup1(int k)//所有1、2函数除不一样的几行外可以合并成一个函数
{
	f1[k] = f1[lc] + f1[rc];
}
void pushup2(int k)
{
	f2[k] = gcd(f2[lc], f2[rc]);
}
void build1(int l, int r, int k)//以下均同理
{
	if(l == r)
	{
		f1[k] = a[l];
		return;
	}
	int mid = (l + r) / 2;
	build1(l, mid, lc);
	build1(mid + 1, r, rc);
	pushup1(k);
}
void build2(int l, int r, int k)
{
	if(l == r)
	{
		f2[k] = b[l];
		return;
	}
	int mid = (l + r) / 2;
	build2(l, mid, lc);
	build2(mid + 1, r, rc);
	pushup2(k);
}
void modify1(int l, int r, int q, int k, int d)//单点修改(求和)
{
	if(l == r && l == q)
	{
	    f1[k] += d;
		return;
	}
	int mid = (l + r) / 2;
	if(q <= mid)
	{
		modify1(l, mid, q, lc, d);
	}
	else
	{
		modify1(mid + 1, r, q, rc, d);
	}
	pushup1(k);
}
void modify2(int l, int r, int q, int k, int d)//单点修改(gcd)
{
	if(l == q && r == q)
	{
		f2[k] += d;
		return;
	}
	int mid = (l + r) / 2;
	if(q <= mid)
	{
		modify2(l, mid, q, lc, d);
	}
	else
	{
		modify2(mid + 1, r, q, rc, d);
	}
	pushup2(k);
}
int query1(int l, int r, int ql, int qr, int k)//区间求和
{
	if(l >= ql && r <= qr)
	{
		return f1[k];
	}
	int mid = (l + r) / 2;
	if(qr <= mid)
	{
		return query1(l, mid, ql, qr, lc); 
	}
	else if(ql >= mid + 1)
	{
		return query1(mid + 1, r, ql, qr, rc);
	}
	else
	{
	    return query1(l, mid , ql, mid, lc) + query1(mid + 1, r, mid + 1, qr, rc);
	}
}
int query2(int l, int r, int ql, int qr, int k)//区间gcd
{
	if(l >= ql && r <= qr)
	{
		return f2[k];
	}
	int mid = (l + r) / 2;
	if(qr <= mid)
	{
		return query2(l, mid, ql, qr, lc);
	}
	else if(ql >= mid + 1)
	{
		return query2(mid + 1, r, ql, qr, rc);
	}
	else
	{
		return gcd(query2(l, mid, ql, mid, lc), query2(mid + 1, r, mid + 1, qr, rc));
	}
}
signed main()
{
	scanf("%lld%lld", &n, &m);
	rep(i, 1, n)
	{
		scanf("%lld", &t[i]);
		a[i] = t[i] - t[i - 1];
		b[i] = t[i] - t[i - 1];
	}
	build1(1, n, 1);
	build2(1, n, 1);
	while(m--)
	{
		char opt;
		cin>>opt;
		int x, y, z;
		if(opt == 'C')
		{
			scanf("%lld%lld%lld", &x, &y, &z);
			modify1(1, n, x, 1, z);
			modify2(1, n, x, 1, z);
			if(y + 1 <= n)//特判差分数组越界
		        {
		    	    modify1(1, n, y + 1, 1, -z);
			    modify2(1, n, y + 1, 1, -z);
		        }
		}
		else
		{
			scanf("%lld%lld", &x, &y);
			if(x == y && x == n)//输出答案前也应判断越界问题,若只是求最后一个数的gcd便是它本身
			{
			    printf("%d\n", query1(1, n, 1, n, 1));
			    continue;
			}
			int temp = gcd(query1(1, n, 1, x, 1), query2(1, n, x + 1, y, 1));
			printf("%lld\n", temp);  
		}
	}
	return 0; 
}
  
posted @ 2022-06-26 23:48  panjx  阅读(137)  评论(0编辑  收藏  举报