题目:BZOJ - 5028
题解:
扩展裴蜀定理+差分+线段树
求从l到r的最小能得到的油量就是求l~r范围内a[i]的gcd
由性质gcd(a,b)=gcd(a,b-a)可得区间gcd可变为:
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] )。
下面谈谈如何证明:
由于 gcd 的性质:
gcd(a, b) = gcd(a, a-b) 其中 a > b;
简单证明:
令 d = gcd(a, b);
a = d*t1; b = d*t2;
两式相减:a-b = d*(t1-t2),所以 gcd(a, a-b) = d
这样对[L, R]区间加, 只要L处加, R+1处减就可以了。
下面再谈谈为什么只要处理L处加,R+1处减:
gcd(a1, a2 - a1, a3 - a2, a4 - a3, a5 - a4)
因为当对a1, a2, ….a4(L=1,R=4)处加v时, a1加上了v, 第二项(a2+v) - (a1+v)是不变的,(a3+v) - (a2+v),(a4+v) - (a3+v)也是不变,的,a5 - (a4+v)会少了一个(-v),所以就是说中间的差分是不变的,两头的差分需要更改,那么就要在L处增加一个v,R+1处需要加上一个(-v),这样就实现了L~R的增加操作,因为我求的是差分的gcd,中间的值不会变就不需要更改了
当我们求gcd(l, l + 1, …r)的时候,只要求gcd(al, a(l+1) - al, a(l + 2) - a(l +1) ….ar - a(r - 1));
所以我们求gcd[l, r]只需要分布求,先求得gcd[l + 1, r],再求1~l的和,因为1~l的差分和就是al。
(比如说:L=3:a1,a2-a1,a3-a2,那么a3的值就是前1~l的差分和)
所以总的看来,我这个题目只需要维护的就是区间的gcd跟和,然后区间更新L跟R+1位置的差分值,查询1~L的差分和,查询L+1~R的差分gcd,再求这两个的gcd即可
代码:
#include
#define N 100005
#define L node<<1
#define R node<<1|1
using namespace std;
int n,m;
int a[N];
struct ljh
{
int sum,g,l,r;
}e[N<<2];
int gcd(int a,int b)
{
return (b==0)?a:gcd(b,a%b);
}
void pushup(int node)
{
e[node].sum=(e[L].sum+e[R].sum);
e[node].g=gcd(e[L].g,e[R].g);
}
void build(int node,int l,int r)
{
e[node].l=l;
e[node].r=r;
if(l==r)
{
e[node].g=e[node].sum=a[l];
return ;
}
int m=(l+r)>>1;
build(L,l,m);
build(R,m+1,r);
pushup(node);
}
void update(int node,int pos,int k)
{
if(e[node].l==e[node].r)
{
e[node].g+=k;
e[node].sum+=k;
return ;
}
int m=(e[node].l+e[node].r)>>1;
if(pos<=m)update(L,pos,k);
else update(R,pos,k);
pushup(node);
}
int query_g(int node,int x,int y)
{
int ans=0;
if(x<=e[node].l&&e[node].r<=y)
{
return e[node].g;
}
int m=(e[node].l+e[node].r)>>1;
if(x<=m)ans=gcd(ans,query_g(L,x,y));
if(y>m)ans=gcd(ans,query_g(R,x,y));
return ans;
}
int query_s(int node,int x,int y)
{
int ans=0;
if(x<=e[node].l&&e[node].r<=y)
{
return e[node].sum;
}
int m=(e[node].l+e[node].r)>>1;
if(x<=m)ans+=query_s(L,x,y);
if(y>m)ans+=query_s(R,x,y);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=n;i>=2;i--)a[i]-=a[i-1];
build(1,1,n);
while(m--)
{
int op,x,y,z;
scanf("%d%d%d",&op,&x,&y);
if(op==1)
{
printf("%d\n",gcd(query_g(1,x+1,y),query_s(1,1,x)));
}
else
{
scanf("%d",&z);
update(1,x,z);
if(y