线段树区间操作
维护区间gcd
给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。//区间修改
2、“Q l r”,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。
Solution
-
gcd(a,b)=gcd(a,b−a)
更相减损术,可以推广到多个数的情况。
更相减损术其实是欧几里得算法的一个特例。即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同样的效果。
然后差分就可以把区间加减变成单点加减。可以用没有lazy的线段树来做。
再维护一个差分,做成树状数组或者线段树,用来维护每个数的值。 -
gcd(a,b)=gcd(a,−b)
在数值加减的过程中可能会产生负数,而约定gcd是没有负数的,所以需要用这个式子来搞定负数。
具体来说,就是在每次查询或者更新的时候,如果遇到了负数,就把它取反进行运算。
注意只能进行运算而不能直接把线段树的叶子节点取反。因为直接把叶子取反会对今后的加减操作造成影响。
虽然gcd(a,b)=gcd(a,−b)gcd(a,b)=gcd(a,−b)但是(a+1,b)和(a+1,−b)不一定相等。
差分操作是a[x]+d,a[y+1]-d;这个时候就可能出现y+1越界的情况。需要及时特判掉。
由于gcd 的性质,易证不会超过logn???
友情提示long long 莫忘
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
#define ls p<<1
#define rs p<<1|1
#define N 500500
struct node{
ll l,r,dat;
}t[N*4];
ll a[N],c[N],b[N];
ll n,m;
inline ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
inline ll jue(ll x){
if(x<0)return -x;
else return x;
}
void build(int p,ll l,ll r){
t[p].l=l;t[p].r=r;
if(l==r){
t[p].dat=c[l];return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
t[p].dat=gcd(t[ls].dat,t[rs].dat);
}
void update(int p,ll x,ll v){
if(x>n)return;
if(t[p].l==t[p].r){
t[p].dat+=v;return;
}
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid)update(ls,x,v);
else update(rs,x,v);
t[p].dat=gcd(t[ls].dat,t[rs].dat);
}
ll query(int p,ll l,ll r){
if(l>r)return 0;
if(l<=t[p].l&&t[p].r<=r)return jue(t[p].dat);
int mid=(t[p].l+t[p].r)>>1;
ll f1=0,f2=0;
if(l<=mid)f1=query(ls,l,r);
if(mid<r)f2=query(rs,l,r);
return jue(gcd(f1,f2));
}
ll add(ll x,ll v,ll *b){
for(;x<=n;x+=x&(-x))b[x]+=v;
}
ll ask(ll x,ll *b){
ll res=0;
for(;x;x-=x&(-x))res+=b[x];
return res;
}
int main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
c[i]=a[i]-a[i-1];
}
build(1,1,n);
char ch[5];
ll xx,yy,v;
while(m--){
scanf("%s",ch);
if(ch[0]=='Q'){
scanf("%lld%lld",&xx,&yy);
printf("%lld\n",gcd(a[xx]+ask(xx,b),query(1,xx+1,yy)));
}else{
scanf("%lld%lld%lld",&xx,&yy,&v);
update(1,xx,v);
update(1,yy + 1,-v);
add(xx,v,b);add(yy+1,-v,b);
}
}
return 0;
}
区间取模
和下面的类似
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
#define inf 0x3f3f3f3f
#define ls p<<1
#define rs p<<1|1
inline int read(){
int res=0;bool f=1;char c;c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){res=res*10+c-'0';c=getchar();}
return f*res;
}
int minn(int x,int y){
return x>y?y:x;
}
struct tree{
int l,r,w;
}t[4*N];
int val[4*N],n,m,x,y,z;
void build(int l,int r,int p){
t[p].l=l;
t[p].r=r;
if(t[p].l==t[p].r){
t[p].w=val[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
t[p].w=minn(t[ls].w,t[rs].w);
}
void query(int p,int ql,int qr){
if(t[p].l==t[p].r){
x%=t[p].w;
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(ql<=mid&&x>=t[ls].w)query(ls,ql,qr);
if(qr>mid&&x>=t[rs].w)query(rs,ql,qr);
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)
val[i]=read();
build(1,n,1);
for(int i=1;i<=m;i++){
x=read();y=read();z=read();
query(1,y,z);
printf("%d\n",x);
}
return 0;
}
区间开根号
sqrt(1)=1,只有最大值大于1时才有修改的必要,维护最大值和sum即可
!!!血与泪的教训。。。mx[]忘开根号。。。。T飞了
啊啊啊啊啊啊
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long LL;
#define ls (p<<1)
#define rs (p<<1|1)
const int N=500005;
inline LL read(){
LL x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
inline int read1(){
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int n,m;
LL sum[N],mx[N],a[N];
LL max(LL a,LL b){
return a>b?a:b;
}
void build(int l,int r,int p){
if(l==r){
sum[p]=mx[p]=a[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
sum[p]=sum[ls]+sum[rs];
mx[p]=max(mx[ls],mx[rs]);
}
void modify(int l,int r,int L,int R,int p){
if(mx[p]<=1) return;
if(l==r){
sum[p]=(LL)sqrt(sum[p]);
mx[p]=(LL)sqrt(mx[p]);
return;
}
int mid=(l+r)>>1;
if(L<=mid) modify(l,mid,L,R,ls);
if(R>mid) modify(mid+1,r,L,R,rs);
sum[p]=sum[ls]+sum[rs];
mx[p]=max(mx[ls],mx[rs]);
}
LL query(int l,int r,int L,int R,int p){
if(L<=l&&r<=R) return sum[p];
int mid=(l+r)>>1;
LL res=0;
if(L<=mid) res+=query(l,mid,L,R,ls);
if(R>mid) res+=query(mid+1,r,L,R,rs);
return res;
}
int main(){
n=read1();
for(int i=1;i<=n;i++) a[i]=read();
build(1,n,1);
m=read1();
for(int i=1,op,x,y;i<=m;i++){
op=read1();x=read1();y=read1();
if(op==0){
if(x>y)swap(x,y);
modify(1,n,x,y,1);
}else{
if(x>y)swap(x,y);
printf("%lld\n",query(1,n,x,y,1));
}
}
return 0;
}
区间修改为正约数个数
预处理,单点暴力修改
预处理一个d[ ]
for(int i=1;i<=1000000;i++)
for(int j=1;i*j<=1000000;j++)
d[i*j】++;
注意到, if 区间a[i]最大值 <=2,则不用修改
else 暴力修改
由于d[x]下降很快,几次后便降到<=2,所以复杂度优秀(大约是nlogn?)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ls p<<1
#define rs p<<1|1
const int N=330000;
ll d[1000005]={0};
int n,m;
ll a[N];
ll t[N*4],mx[N*4];
inline void pushup(int p){
t[p]=t[ls]+t[rs];
mx[p]=max(mx[ls],mx[rs]);
}
inline void build(int l,int r,int p){
if(l==r){
mx[p]=a[l];t[p]=a[l];return;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
pushup(p);
}
inline void update(int l,int r,int p,int ql,int qr){
if(l>qr||r<ql)return;
if(mx[p]<=2)return;
if(ql<=l&&r<=qr&&l==r){
t[p]=d[t[p]];mx[p]=t[p];return;
}
int mid=(l+r)>>1;
update(l,mid,ls,ql,qr);
update(mid+1,r,rs,ql,qr);
pushup(p);
}
ll query(int l,int r,int p,int ql,int qr){
if(l>qr||r<ql)return 0;
if(l>=ql&&r<=qr)return t[p];
int mid=(l+r)>>1;
return query(l,mid,ls,ql,qr)+query(mid+1,r,rs,ql,qr);
}
int main(){
for(int i=1;i<=1000000;i++)
for(int j=1;i*j<=1000000;j++)
d[i*j]++;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,n,1);
while(m--){
int opt=0,x,y;
scanf("%d%d%d",&opt,&x,&y);
if(opt==1) update(1,n,1,x,y);
else printf("%lld\n",query(1,n,1,x,y));
}
return 0;
}
01串+翻转
维护一个01串,一开始全部都是0
3种操作
1.把一个区间都变为1
2.把一个区间都变为0
3.把一个区间的所有数字翻转过来
每次操作完成之后询问区间最小的0的位置
l,r<= $ 10^{18} $ __离散化
#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 400005
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
int n,len,opt[N];
int atag[N],xtag[N];
ll ql[N],qr[N],b[N];
struct tree{
int sum,rev,tag;
}t[N<<2];
inline void pushup(int p){
t[p].sum=t[ls].sum+t[rs].sum;
}
void adjust1(int p,int l,int r,int v){
t[p].rev=0;
t[p].tag=v--;
t[p].sum=v*(r-l+1);
}
void adjust2(int p,int l,int r){
t[p].sum=r-l+1-t[p].sum;
t[p].rev^=1;
}
void pushdown(int p,int l,int r){
int mid=(l+r)>>1;
if(t[p].tag){
adjust1(ls,l,mid,t[p].tag);
adjust1(rs,mid+1,r,t[p].tag);
t[p].tag=0;
}
if(t[p].rev){
adjust2(ls,l,mid);
adjust2(rs,mid+1,r);
t[p].rev=0;
}
}
void modify(int p,int l,int r,int L,int R,int v){
if(L<=l&&r<=R){
adjust1(p,l,r,v);return;
}
pushdown(p,l,r);
int mid=(l+r)>>1;
if(L<=mid) modify(ls,l,mid,L,R,v);
if(R>mid) modify(rs,mid+1,r,L,R,v);
pushup(p);
}
void rever(int p,int l,int r,int L,int R){
if(L<=l&&r<=R){
adjust2(p,l,r);return;
}
pushdown(p,l,r);
int mid=(l+r)>>1;
if(L<=mid) rever(ls,l,mid,L,R);
if(R>mid) rever(rs,mid+1,r,L,R);
pushup(p);
}
int query(int p,int l,int r){
if(l==r)return l;
pushdown(p,l,r);
int mid=(l+r)>>1,ans;
if(t[ls].sum!=mid-l+1) ans=query(ls,l,mid);
else ans=query(rs,mid+1,r);
pushup(p);
return ans;
}
int main(){
scanf("%d",&n);b[++len]=1;
for(int i=1;i<=n;i++){
scanf("%d%lld%lld",&opt[i],&ql[i],&qr[i]);
b[++len]=ql[i];b[++len]=qr[i];
b[++len]=ql[i]+1;b[++len]=qr[i]+1;
}
sort(b+1,b+1+len);len=unique(b+1,b+1+len)-b-1;//排序去重
for(int i=1;i<=n;i++){
ql[i]=lower_bound(b+1,b+1+len,ql[i])-b;//离散化
qr[i]=lower_bound(b+1,b+1+len,qr[i])-b;
if(opt[i]==1) modify(1,1,len,ql[i],qr[i],2);
else if(opt[i]==2) modify(1,1,len,ql[i],qr[i],1);
else rever(1,1,len,ql[i],qr[i]);
printf("%lld\n",b[query(1,1,len)]);
}
return 0;
}