小阳的贝壳
题目描述 :
小阳手中一共有 n 个贝壳,每个贝壳都有颜色,且初始第 i 个贝壳的颜色为 col_i
现在小阳有 3 种操作:
1 l r x:给 [l,r] 区间里所有贝壳的颜色值加上 x 。
2 l r:询问 [l,r] 区间里所有相邻贝壳 颜色值的差(取绝对值) 的最大值(若 l = r 输出 0)。
3 l r :询问 [l,r]区间里所有贝壳颜色值的最大公约数。
输入描述:
第一行输入两个正整数 n,m,分别表示贝壳个数和操作个数。
第二行输入 n 个数 col_i 表示每个贝壳的初始颜色。
第三到第 m + 2 行,每行第一个数为 opt,表示操作编号。接下来的输入的变量与操作编号对应。
输出描述:
共 m 行,对于每个询问(操作 2 和操作 3)输出对应的结果。
1 ≤ n , m ≤ 1 0 5 , 1 ≤ c o l i , x ≤ 1 0 3 , 1 ≤ o p t ≤ 3 , 1 ≤ l ≤ r ≤ n 1≤n,m≤10^5,1≤col_i,x≤10^3 ,1≤opt≤3,1≤l≤r≤n 1≤n,m≤105,1≤coli,x≤103,1≤opt≤3,1≤l≤r≤n
思路: 首先肯定是线段树可以做,单看操作1,想到了线段树的区间修改,但是区间的最大公约数pushdown的过程好像没什么思路。而且我们不用pushdown就尽量不用。另外对于区间修改我们也常用差分来转化,从而避免区间修改,并且此题维护的差值也提示我们使用差分转化。
对于操作1,差分后直接单点修改即可;
对于操作2,从差分的定义出发,在
[
l
,
r
]
内
[l,r]内
[l,r]内 ,求
∣
c
o
l
i
−
c
o
l
i
−
1
∣
|col_i-col_{i-1}|
∣coli−coli−1∣ 的最大值其实就是在
[
l
+
1
,
r
]
[l+1,r]
[l+1,r]中求
∣
d
i
∣
|d_i|
∣di∣ 的最大值,所以我们直接维护差分数组中元素的绝对值的最大值即可。
对于操作3,先来了解一下 gcd 的一些性质:
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
)
(
交
换
律
)
g
c
d
(
−
a
,
b
)
=
g
c
d
(
a
,
b
)
g
c
d
(
a
,
a
)
=
∣
a
∣
g
c
d
(
a
,
0
)
=
∣
a
∣
g
c
d
(
a
,
1
)
=
1
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
b
)
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
−
b
)
如
果
有
附
加
的
一
个
自
然
数
m
,
则
:
g
c
d
(
m
a
,
m
b
)
=
m
∗
g
c
d
(
a
,
b
)
(
分
配
律
)
g
c
d
(
a
+
m
b
,
b
)
=
g
c
d
(
a
,
b
)
gcd(a,b)=gcd(b,a) (交换律)\\ gcd(-a,b)=gcd(a,b) \\ gcd(a,a)=|a|\\ gcd(a,0)=|a|\\ gcd(a,1)=1\\ gcd(a,b)=gcd(b, a \mod b)\\ gcd(a,b)=gcd(b, a-b)\\ 如果有附加的一个自然数m,\\ 则: gcd(ma,mb)=m * gcd(a,b) (分配律)\\ gcd(a+mb ,b)=gcd(a,b)
gcd(a,b)=gcd(b,a)(交换律)gcd(−a,b)=gcd(a,b)gcd(a,a)=∣a∣gcd(a,0)=∣a∣gcd(a,1)=1gcd(a,b)=gcd(b,amodb)gcd(a,b)=gcd(b,a−b)如果有附加的一个自然数m,则:gcd(ma,mb)=m∗gcd(a,b)(分配律)gcd(a+mb,b)=gcd(a,b)
其中更相减损法:
g
c
d
(
a
,
b
)
=
g
c
d
(
a
,
∣
b
−
a
∣
)
gcd(a,b)=gcd(a,|b-a|)
gcd(a,b)=gcd(a,∣b−a∣)
所以对于操作3我们需要维护区间和、区间 gcd 即可(当然维护的这些都是差分数组的信息。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
struct Node{
int l,r;
int sum,Max,gcd;
}tr[N*4];
int n,m;
int w[N],ca[N];
void pushup(int u){
tr[u].sum = tr[u<<1].sum+tr[u<<1|1].sum;
tr[u].Max = max(tr[u<<1].Max,tr[u<<1|1].Max);
tr[u].gcd = __gcd(tr[u<<1].gcd,tr[u<<1|1].gcd);
}
void build(int u,int l,int r)
{
if(l==r) tr[u] = {l,r,ca[l],abs(ca[l]),abs(ca[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);
}
}
void modify(int u,int x,int c)
{
if(tr[u].l==x&&tr[u].r==x){
tr[u].sum+=c; //单点修改差分数组
tr[u].gcd = tr[u].Max = abs(tr[u].sum);
}
else {
int mid = tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,c);
else modify(u<<1|1,x,c);
pushup(u);
}
}
int query_max(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].Max;
int mid = tr[u].l+tr[u].r>>1;
int ans=0;
if(l<=mid) ans = max(query_max(u<<1,l,r),ans);
if(r>mid) ans = max(query_max(u<<1|1,l,r),ans);
return ans;
}
int query_gcd(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].gcd;
int mid = tr[u].l+tr[u].r>>1;
int ans = 0;
if(l<=mid) ans = __gcd(query_gcd(u<<1,l,r),ans);
if(r>mid) ans = __gcd(query_gcd(u<<1|1,l,r),ans);
return ans;
}
int query_sum(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;
int mid =tr[u].l+tr[u].r>>1;
int ans=0;
if(l<=mid) ans += query_sum(u<<1,l,r);
if(r>mid) ans += query_sum(u<<1|1,l,r);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<=n;i++) ca[i] = w[i] - w[i-1]; //构造差分数组
build(1,1,n);
while(m--){
int t,l,r,x;
scanf("%d%d%d",&t,&l,&r);
if(t==1){
scanf("%d",&x);
modify(1,l,x);
if(r+1<=n) modify(1,r+1,-x); //不能超过 n
}
else if(t==2){
if(l==r) printf("0\n");
else printf("%d\n",query_max(1,l+1,r)); //注意区间
}
else printf("%d\n",__gcd(query_sum(1,1,l),query_gcd(1,l+1,r))); //通过区间第一个元素及差分的gcd求出区间gcd
}
return 0;
}