石中2024寒假集训 Day4
今天学了线段树。要注意:
- 节点 的懒惰标记是代表要下传的,当前节点的懒惰标记应该用于更新子节点并下传给子节点。
- 在查询与更新时都要下传,防止子节点没有及时更新。
例题 1:线段树 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+10;
int a[N],n,m;
struct sgtree{
#define l(i) ((i)<<1)
#define r(i) ((i)<<1|1)
#define mid (l+r>>1)
int tag,v;
}c[N<<2];
void pushu(int p) {
c[p].v = c[l(p)].v+c[r(p)].v;
}
void pushd(int p,int l,int r) {
c[l(p)].tag+=c[p].tag, c[r(p)].tag+=c[p].tag;
c[l(p)].v+=(mid-l+1)*c[p].tag, c[r(p)].v+=(r-mid)*c[p].tag;
c[p].tag = 0;
}
void build(int p,int l,int r) {
c[p].tag = 0;
if(l==r) {c[p].v=a[l];return ;}
build(l(p),l,mid);
build(r(p),mid+1,r);
pushu(p);
}
void upd(int nl,int nr,int p,int l,int r,int k) {
if(nl<=l&&r<=nr) {
c[p].tag+=k;
c[p].v+=(r-l+1)*k;
return ;
}
pushd(p,l,r);
if(nl<=mid) upd(nl,nr,l(p),l,mid,k);
if(nr>mid) upd(nl,nr,r(p),mid+1,r,k);
pushu(p);
}
int ans(int nl,int nr,int p,int l,int r) {
int s = 0;
if(nl<=l&&r<=nr) return c[p].v;
pushd(p,l,r);
if(nl<=mid) s+=ans(nl,nr,l(p),l,mid);
if(nr>mid) s+=ans(nl,nr,r(p),mid+1,r);
return s;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
while(m--) {
int op,x,y,k;
cin>>op>>x>>y;
if(op==1) {
cin>>k;
upd(x,y,1,1,n,k);
}
else {
cout<<ans(x,y,1,1,n)<<"\n";
}
}
return 0;
}
动态开点
假如要存的值域是 ,但是只有 个数呢?可以离散化,但是来回地映射增加实现难度。
在上文的模板中,节点 的儿子是 。
这就导致空间的浪费。如果我们像链式前向星那样,就可以减少空间使用。以下是一个动态开点权值线段树的简单题。
查询 小值可以用二分实现。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int a[N],n,m,ix,rt;
struct sgtree{
#define l(i) (c[i].ls)
#define r(i) (c[i].rs)
#define mid (l+r>>1)
int tag,v,ls,rs;
}c[N*80];
void pushu(int p) {
c[p].v = c[l(p)].v+c[r(p)].v;
}
void pushd(int p,int l,int r) {
if(!l(p)) l(p)=++ix,c[l(p)].tag=0;//开点
if(!r(p)) r(p)=++ix,c[r(p)].tag=0;
c[l(p)].tag+=c[p].tag, c[r(p)].tag+=c[p].tag;
c[l(p)].v+=(mid-l+1)*c[p].tag, c[r(p)].v+=(r-mid)*c[p].tag;
c[p].tag = 0;
}
//void build(int p,int l,int r) {
// c[p].tag = 0;
// if(l==r) {c[p].v=a[l];return ;}
// build(l(p),l,mid);
// build(r(p),mid+1,r);
//}
void upd(int nl,int nr,int &p,int l,int r,int k) {
if(!p) p=++ix,c[p].tag=0;//开点
if(nl<=l&&r<=nr) {
c[p].tag+=k;
c[p].v+=(r-l+1)*k;
return ;
}
pushd(p,l,r);
if(nl<=mid) upd(nl,nr,l(p),l,mid,k);
if(nr>mid) upd(nl,nr,r(p),mid+1,r,k);
pushu(p);
}
int ans(int nl,int nr,int p,int l,int r) {
if(!p) return 0;//如果当前点没开,也就没有访问过,无需记录。
int s = 0;
if(nl<=l&&r<=nr) return c[p].v;
pushd(p,l,r);
if(nl<=mid) s+=ans(nl,nr,l(p),l,mid);
if(nr>mid) s+=ans(nl,nr,r(p),mid+1,r);
return s;
}
signed main() {
cin>>n;
for(int i=1;i<=n;i++) {
cin>>a[i];
upd(a[i],a[i],rt,0,1e9,1);
if(i&1) {
int l=-1,r=1e9+1;
while(l+1<r) {
// cout<<mid<<"\n";
if(ans(0,mid,rt,0,1e9)<i/2+1) l=mid;
else r=mid;
}
cout<<l+1<<"\n";
}
}
return 0;
}
应用
线段树可以做一些树状数组做不到的东西,比如区间最值,区间加区间平方和之类的。以下是练习题。
区间加,区间方差。其实是推式子题。方差的公式为:。化简一下就可以得到方差为
平方和的求法:
使用线段树可以方便地维护,注意平方和要先更新。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+10;
double a[N];int n,m;
struct sgtree {
#define l(i) ((i)<<1)
#define r(i) ((i)<<1|1)
#define mid (l+r>>1)
double tag, v;
}c[N<<2],d[N<<2];//和,平方和
void pushu(int p) {c[p].v = c[l(p)].v+c[r(p)].v; d[p].v=d[l(p)].v+d[r(p)].v;}
void pushd(int p,int l,int r) {
d[l(p)].tag+=d[p].tag, d[r(p)].tag+=d[p].tag;
d[l(p)].v+=2*d[p].tag*c[l(p)].v+(mid-l+1)*d[p].tag*d[p].tag,
d[r(p)].v+=2*d[p].tag*c[r(p)].v+(r-mid)*d[p].tag*d[p].tag;
d[p].tag = 0;
c[l(p)].tag+=c[p].tag, c[r(p)].tag+=c[p].tag;
c[l(p)].v+=(mid-l+1)*c[p].tag, c[r(p)].v+=(r-mid)*c[p].tag;
c[p].tag = 0;
}
void build(int p,int l,int r) {
c[p].tag = 0, d[p].tag = 0;
if(l==r) {c[p].v=a[l],d[p].v=a[l]*a[l];return ;}
build(l(p),l,mid);
build(r(p),mid+1,r);
pushu(p);
}
void upd(int nl,int nr,int p,int l,int r,double k) {
if(nl<=l&&r<=nr) {
d[p].tag+=k;
d[p].v+=2*k*c[p].v+(r-l+1)*k*k;
c[p].tag+=k;
c[p].v+=(r-l+1)*k;
return ;
}
pushd(p,l,r);
if(nl<=mid) upd(nl,nr,l(p),l,mid,k);
if(nr>mid) upd(nl,nr,r(p),mid+1,r,k);
pushu(p);
}
double ansc(int nl,int nr,int p,int l,int r) {
double s = 0;
if(nl<=l&&r<=nr) return c[p].v;
pushd(p,l,r);
if(nl<=mid) s+=ansc(nl,nr,l(p),l,mid);
if(nr>mid) s+=ansc(nl,nr,r(p),mid+1,r);
return s;
}
double ansd(int nl,int nr,int p,int l,int r) {
double s = 0;
if(nl<=l&&r<=nr) return d[p].v;
pushd(p,l,r);
if(nl<=mid) s+=ansd(nl,nr,l(p),l,mid);
if(nr>mid) s+=ansd(nl,nr,r(p),mid+1,r);
return s;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
while(m--) {
int op,x,y;double k;
cin>>op>>x>>y;
if(x>y) swap(x,y);
if(op==1) {
cin>>k;
upd(x,y,1,1,n,k);
}
if(op==2) {
printf("%.4lf\n",ansc(x,y,1,1,n)/(y-x+1));
}
if(op==3) {
double xb=ansc(x,y,1,1,n)/(y-x+1);
printf("%.4lf\n",ansd(x,y,1,1,n)/(y-x+1)-xb*xb);
}
}
return 0;
}
T2
给一个长度是 的数列 接下来在数列 上进行如下 次操作,操作类型共两种:
-
1 x y求 。 -
2 x y z将 分别 。
我们对于区间异或束手无策。但看到位运算就该想到按二进制位拆分来算。
如果我们一位位地统计,那么只需知道所有数中该位上 的个数。根据异或的性质,如果 在该位为 ,没有影响,如果为 ,那么 序列会被取反,也就意味着我们可以维护每个区间 1 的个数了。我们只要开大概 20 个线段树维护,统计答案时按位加权求和即可。
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int a[N],n,m;
struct sgtree{
#define l(i) ((i)<<1)
#define r(i) ((i)<<1|1)
#define mid (l+r>>1)
struct sg{
int tag,v;
}c[N<<2];
//tag:当前区间^1 的个数 区间 1 的个数
void pushu(int p) {
c[p].v = c[l(p)].v+c[r(p)].v;
}
void pushd(int p,int l,int r) {
c[l(p)].tag+=c[p].tag, c[r(p)].tag+=c[p].tag;
// cout<<l<<" "<<r<<"\n"<<c[l(p)].tag<<" "<<c[r(p)].tag<<endl;
if(c[p].tag&1) c[l(p)].v=(mid-l+1)-c[l(p)].v;
if(c[p].tag&1) c[r(p)].v=(r-mid)-c[r(p)].v;
c[p].tag = 0;
}
void build(int p,int l,int r) {
c[p].tag = 0;
if(l==r) {c[p].v=a[l];return ;}
build(l(p),l,mid);
build(r(p),mid+1,r);
pushu(p);
}
void upd(int nl,int nr,int p,int l,int r) {
if(nl<=l&&r<=nr) {
c[p].tag++;
c[p].v=(r-l+1)-c[p].v;
return ;
}
pushd(p,l,r);
if(nl<=mid) upd(nl,nr,l(p),l,mid);
if(nr>mid) upd(nl,nr,r(p),mid+1,r);
pushu(p);
}
int ans(int nl,int nr,int p,int l,int r) {
int s = 0;
if(nl<=l&&r<=nr) return c[p].v;
pushd(p,l,r);
if(nl<=mid) s+=ans(nl,nr,l(p),l,mid);
if(nr>mid) s+=ans(nl,nr,r(p),mid+1,r);
return s;
}
}f[22];
int t[N];
int op,x,y,k;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n;
int mt=0;
for(int i=1;i<=n;i++) {
cin>>t[i]; mt=max(mt,t[i]);
}
for(int i=0;i<=__lg(mt);i++) {
int k = 1<<i;
for(int j=1;j<=n;j++) {
a[j]=(t[j]>>i)&1;
}
// for(int i=1;i<=n;i++) cout<<a[i]<<" ";
// cout<<endl;
f[i].build(1,1,n);
}
int ad=0;
// for(int i=0;i<18;i++) cout<<f[i].ans(1,n,1,1,n)*(1<<i)<<"\n";
// cout<<"FU:"<<ad<<"\n";
cin>>m;
while(m--) {
cin>>op>>x>>y;
if(op==1) {
long long ans = 0;
for(int i=0;i<=__lg(mt);i++) {
// cout<<"I="<<i<<endl;
ans+=f[i].ans(x,y,1,1,n)*(1LL<<i);
}
cout<<ans<<"\n";
}
else {
cin>>k;
for(int i=0;i<=__lg(k);i++) {
if((k>>i)&1) f[i].upd(x,y,1,1,n);
}
}
}
return 0;
}
/*
5
1 1 1 1 1
4
2 1 3 1
1 1 1
1 1 1
1 1 1
*/

浙公网安备 33010602011771号