一级标题提醒:此题细节很多
1. $gcd(a,b) = gcd(a,b-a)$
在lyd的书里提到的这个性质叫更相减损术,可以推广到多个数的情况。
更相减损术其实是欧几里得算法的一个特例。即$gcd(a-nb,b)=gcd(a,b)$。
$(a,b,c)=((a,b),(b,c))=((a,b-a),(b,c-b))=(a,b-a,b,c-b)$
由于$(b-a,b)=(a,b-a)$所以$(a,b,c)=(a,b-a,c-b)$
有了这个式子说名可以通过维护序列的差分来达到求gcd同样的效果。
比如求$(a,b,c)$只需要知道现在$a$的值,然后知道$(b-a,c-b)$的gcd,再求一个公约数就行了。
-
差分就可以把区间加减变成单点加减。可以用没有lazy的线段树来做。
-
再维护一个差分,做成树状数组或者线段树,用来维护每个数的值。
2. $gcd(a,b)=gcd(a,-b)$
在数值加减的过程中可能会产生负数,而约定gcd是没有负数的,所以需要用这个式子来搞定负数。
具体来说,就是在每次查询或者更新的时候,如果遇到了负数,就把它取反。
注意只能对结果取反而不能直接把线段树的负数叶子节点取反。因为直接把叶子取反会对今后的加减操作造成影响。
虽然$gcd(a,b)=gcd(a,-b)$但是$(a+1,b)$和$(-a+1,b)$不一定相等。
差分操作
差分操作是a[x]+d,a[y+1]-d;这个时候就可能出现y+1越界的情况。需要及时特判掉。
大概就这么多吧。主要是一些小的细节需要注意。比如数据爆int,范围不给全,蒟蒻两行泪
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 23;
struct Node{
int l, r;
ll v, d;
}tr[maxn * 4];
ll a[maxn], b[maxn];
void pushup(Node &u, Node &l, Node &r)
{
u.v = l.v + r.v;
u.d = __gcd(l.d, r.d);
}
void pushup(int u)
{
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
//printf("%d[%d,%d]=%d\n",u,tr[u].l,tr[u].r,tr[u].d);
}
void build(int u, int l, int r)
{
if(l == r) tr[u] = {l, r, b[l], b[l]};
else
{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
ll query(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].d;
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(r <= mid) return query(u << 1, l, r);
else if(l > mid) return query(u << 1 | 1, l, r);
else return __gcd(query(u << 1, l, r), query(u << 1 | 1, l, r));
}
}
ll query2(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(r <= mid) return query2(u << 1, l, r);
else if(l > mid) return query2(u << 1 | 1, l, r);
else return query2(u << 1, l, r) + query2(u << 1 | 1, l, r);
}
}
void modify(int u, int p, ll v)
{
if(tr[u].l == tr[u].r && tr[u].l == p) tr[u].d += v, tr[u].v += v;
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(p <= mid) modify(u << 1, p, v);
else modify(u << 1 | 1, p, v);
pushup(u);
}
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]), b[i] = a[i] - a[i - 1];
build(1, 1, n);
char op[4];
ll op1 = 0, op2 = 0, op3 = 0;
while(m--)
{
scanf("%s%lld%lld", op, &op1, &op2);
if(*op == 'C')
{
scanf("%lld", &op3);
modify(1, op1, op3);
if(op2 + 1 <= n) modify(1, op2 + 1, -op3);
}
else
{
ll t = query2(1, 1, op1); //cout << t << endl;
printf("%lld\n", abs( __gcd(t, query(1, op1 + 1, op2))));
}
}
}
/*
1 2 2 2 2 / 1 3 5 7 9
2 2 2 2 2 / 2 4 6 8 10
2 2 8 -4 2 / 2 4 12 8 10
*/
提示:本题解现在无法通过,不知道作者是不是删了某些语句
这题没考虑好边界情况,当Q的时候,若op1+1>n,则直接输出前缀和即可,不用求gcd,然后就可以ac了
难道不是
op1==op2
时输出前缀和吗支持支持
两个query 应该再加个if (l > r) return 0;
好评:比如数据爆int,
范围不给全,蒟蒻两行泪为什么查询区间(l,r)的gcd不直接query(1,l,r)
query(1,l,r) = gcd(bl,···,br) ≠gcd(al,bl+1,···,br)
为啥这个代码提交后Segmentation Fault了
请问结构体中的v和d分别是代表什么意思呢?原谅小白的阅码能力吧
val
就是某个区间的差分数组 的 和,因为你需要求原值呀,你看通过变换,ans
的第一位还是x
,也就是仍然需要与数组原值取gcd
。d
就是区间gcd
,维护区间gcd
,这样可以使我们更快的get ans
。感谢!
int t = query2(1 , 1 , op1) ;
if(op1 == op2)
cout << t << ‘\n’;
else
cout << abs(gcd(t , query(1 , op1 + 1 , op2))) <<’\n’;
为什么不用懒标记啊
小小蒟蒻弱弱地问一句,这个pushup的定义不会报错吗?后一个pushup调用了前一个pushup,区分开的原因是传参吗?大佬求解
重定义了,不会报错的
谢谢大佬
这个是C++的函数重载,参数不同的同名函数可以区分
为什么gcd(a + 1, -b)和gcd(a + 1, b)不一定相等呢
写错了,已更正
请问数据爆int是指给出的a[i]可能是long long级别吗
操作中可能会longlong
$a_i \le 10^{18}$
(逃)6
%%%