NOIP-6
前言:
线段树简直太迷人了,令人发指……
例题:
1. P3372 【模板】线段树 1
题意:线段树的最基本操作……
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
inline int read()
{
static char ch;
int res=0;
int sign=1;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-') {
sign=-1;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
const int N=2e5;
int a[N<<2];
int sum[N<<2],maxx[N<<2],add[N<<2];
void Add(int rt,int l,int r,int v){
add[rt]+=v;
sum[rt]+=v*(r-l+1);
}
void pushdown(int rt,int l,int r){
if(add[rt]==0) return ;
int mid=l+r>>1;
Add(rt<<1,l,mid,add[rt]);
Add(rt<<1|1,mid+1,r,add[rt]);
add[rt]=0;
return ;
}
void build(int rt,int l,int r){
if(l==r){
sum[rt]=a[l];
return ;
}
int mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
return ;
}
void modify(int rt,int l,int r,int x,int y,int v){
if(l>=x&&r<=y){
add[rt]+=v;
sum[rt]+=v*(r-l+1);
return ;
}
int mid=l+r>>1;
pushdown(rt,l,r);
if(x<=mid) modify(rt<<1,l,mid,x,y,v);
if(mid<y) modify(rt<<1|1,mid+1,r,x,y,v);
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
return ;
}
int query(int rt,int l,int r,int x,int y){
if(l>=x&&r<=y){
return sum[rt];
}
int res=0;
int mid=l+r>>1;
pushdown(rt,l,r);
if(x<=mid) res+=query(rt<<1,l,mid,x,y);
if(mid<y) res+=query(rt<<1|1,mid+1,r,x,y);
return res;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
build(1,1,n);
for(int i=1;i<=m;i++){
int aa,b,c,d;
aa=read(),b=read(),c=read();
if(aa==1){
d=read();
modify(1,1,n,b,c,d);
}else {
cout<<query(1,1,n,b,c)<<endl;
}
}
return 0;
}
2. P3373 【模板】线段树 2
题意:区间加,区间乘。
先乘后加万岁!!!在做乘法的时候同时把加法的Mark乘上这个数,后面做加法的时候直接加。
#include<bits/stdc++.h>
using namespace std;
int p;
long long a[1000007];
struct node{
long long v,mul,add;
}st[400007];
void bt(int root,int l,int r){
st[root].mul=1;
st[root].add=0;
if(l==r){
st[root].v=a[l];
}else{
int m=(l+r)/2;
bt(root*2,l,m);
bt(root*2+1,m+1,r);
st[root].v=st[root*2].v+st[root*2+1].v;
}
st[root].v%=p;
return ;
}
void pushdown(int root,int l,int r)
{
int m=(l+r)/2;
st[root*2].v=(st[root*2].v*st[root].mul+st[root].add*(m-l+1))%p;
st[root*2+1].v=(st[root*2+1].v*st[root].mul+st[root].add*(r-m))%p;
st[root*2].mul=(st[root*2].mul*st[root].mul)%p;
st[root*2+1].mul=(st[root*2+1].mul*st[root].mul)%p;
st[root*2].add=(st[root*2].add*st[root].mul+st[root].add)%p;
st[root*2+1].add=(st[root*2+1].add*st[root].mul+st[root].add)%p;
st[root].mul=1;
st[root].add=0;
return ;
}
void ud1(int root,int stdl,int stdr,int l,int r,long long k)
{
if(l<=stdl&&r>=stdr){
st[root].v=(st[root].v*k)%p;
st[root].mul=(st[root].mul*k)%p;
st[root].add=(st[root].add*k)%p;
return ;
}
pushdown(root,stdl,stdr);
int m=(stdl+stdr)>>1;
if(l<=m)ud1(root*2,stdl,m,l,r,k);
if(m<r)ud1(root*2+1,m+1,stdr,l,r,k);
st[root].v=(st[root*2].v+st[root*2+1].v)%p;
return ;
}
void ud2(int root,int stdl,int stdr,int l,int r,long long k){
if(r<stdl||stdr<l) return ;
if(l<=stdl&&stdr<=r){
st[root].add=(st[root].add+k)%p;
st[root].v=(st[root].v+k*(stdr-stdl+1))%p;
return ;
}
pushdown(root,stdl,stdr);
int m=(stdl+stdr)/2;
ud2(root*2,stdl,m,l,r,k);
ud2(root*2+1,m+1,stdr,l,r,k);
st[root].v=(st[root*2].v+st[root*2+1].v)%p;
return ;
}
long long query(int root,int stdl,int stdr,int l,int r){
if(r<stdl||stdr<l){
return 0;
}
if(l<=stdl&&stdr<=r){
return st[root].v;
}
pushdown(root,stdl,stdr);
int m=(stdl+stdr)/2;
return (query(root*2,stdl,m,l,r)+query(root*2+1,m+1,stdr,l,r))%p;
}
int main()
{ int n,m;
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
bt(1,1,n);
while(m){
int chk;
scanf("%d",&chk);
int x,y;
long long k;
if(chk==1){
cin>>x>>y>>k;
ud1(1,1,n,x,y,k);
}else if(chk==2){
cin>>x>>y>>k;
ud2(1,1,n,x,y,k);
}else if(chk==3){
scanf("%d%d",&x,&y);
printf("%lld\n",query(1,1,n,x,y));
}
m--;
}
return 0;
}
3.P3870 [TJOI2009] 开关
题意:区间异或,区间求和
用分块……
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int a[maxn],b[maxn],tag[maxn],ans[maxn];
int n,m,c,r,t,x,y,z,sq;
void update(int x,int y){
for(int i=x;i<=min(y,b[x]*sq);i++){//左散块
ans[b[x]]-=(a[i]^tag[b[x]]);
a[i]^=1;
ans[b[x]]+=(a[i]^tag[b[x]]);
}
if(b[x]!=b[y]){//右散块
for(int i=(b[y]-1)*sq+1;i<=y;i++){
ans[b[y]]-=(a[i]^tag[b[y]]);
a[i]^=1;
ans[b[y]]+=(a[i]^tag[b[y]]);
}
}
for(int i=b[x]+1;i<=b[y]-1;i++){
tag[i]^=1;
ans[i]=sq-ans[i];
}
}
int query(int x,int y){
int res=0;
for(int i=x;i<=min(y,b[x]*sq);i++){
res+=(a[i]^tag[b[x]]);
}
if(b[x]!=b[y]){
for(int i=(b[y]-1)*sq+1;i<=y;i++){
res+=a[i]^tag[b[y]];
}
}
for(int i=b[x]+1;i<=b[y]-1;i++){
res+=ans[i];
}
return res;
}
int main()
{
cin>>n>>m;
sq=sqrt(n);//块长
for(int i=1;i<=n;i++){
b[i]=(i-1)/sq+1;//每个点的块区
}
for(int i=1;i<=m;i++){
int f,a,b;
cin>>f>>a>>b;
if(f==0){
update(a,b);
}else{
cout<<query(a,b)<<endl;
}
}
return 0;
}
4.P1253 [yLOI2018] 扶苏的问题
题意:
给定一个长度为 nn 的序列 aa,要求支持如下三个操作:
- 给定区间 [l, r][l,r],将区间内每个数都修改为 xx。
- 给定区间 [l, r][l,r],将区间内每个数都加上 xx。
- 给定区间 [l, r][l,r],求区间内的最大值。
维护两个add数组,一个是覆盖,一个是加上。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
inline int read()
{
static char ch;
int res=0;
int sign=1ll;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-') {
sign=-1ll;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10ll+ch-'0';
}
return res*sign;
}
const int N=1e6+10;
int a[N];
int sum[N<<3ll],add[N<<3ll],add2[N<<3ll];
void Add(int rt,int l,int r,int v){//加上
sum[rt]+=v;
add[rt]+=v;
}
void Add2(int rt,int l,int r,int v){//覆盖
add[rt]=0;
sum[rt]=v;
add2[rt]=v;
}
void pushdown1(int rt,int l,int r){
if(add2[rt]!=-1145141919810) {
int mid=l+r>>1;
Add2(rt<<1,l,mid,add2[rt]);
Add2(rt<<1|1,mid+1,r,add2[rt]);
add2[rt]=-1145141919810;
}
return ;
}
void pushdown2(int rt,int l,int r){//先覆盖再加
if(add[rt]!=0){
pushdown1(rt,l,r);
int mid=l+r>>1ll;
Add(rt<<1ll,l,mid,add[rt]);
Add(rt<<1ll|1ll,mid+1ll,r,add[rt]);
add[rt]=0;
}
return ;
}
void pushdown(int rt,int l,int r){
pushdown1(rt,l,r);
pushdown2(rt,l,r);
}
void pushup(int rt){
sum[rt]=max(sum[rt<<1],sum[rt<<1|1]);
}
void build(int rt,int l,int r){
if(l==r){
sum[rt]=a[l];
add2[rt]=-1145141919810;//初始状态
add[rt]=0;
return ;
}
int mid=l+r>>1ll;
build(rt<<1ll,l,mid);
build(rt<<1ll|1ll,mid+1ll,r);
pushup(rt);
return ;
}
void modify(int rt,int l,int r,int x,int y,int v,int op){
if(l>=x&&r<=y){
if(op==2){
pushdown1(rt,l,r);
add[rt]+=v;
sum[rt]+=v;
}
if(op==1){
add[rt]=0;
add2[rt]=v;
sum[rt]=v;
}
return ;
}
int mid=l+r>>1ll;
pushdown(rt,l,r);
if(x<=mid) modify(rt<<1ll,l,mid,x,y,v,op);
if(mid<y) modify(rt<<1ll|1ll,mid+1ll,r,x,y,v,op);
pushup(rt);
return ;
}
int query(int rt,int l,int r,int x,int y){
if(l>=x&&r<=y){
return sum[rt];
}
int res=0;
int mid=l+r>>1ll;
pushdown(rt,l,r);
res=-1e13;
if(x<=mid) res=max(res,query(rt<<1ll,l,mid,x,y));
if(mid<y) res=max(res,query(rt<<1ll|1ll,mid+1ll,r,x,y));
return res;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
build(1,1,n);
for(int i=1;i<=n<<2;i++)
{
add2[i]=-1145141919810;//注意覆盖不能是0
}
for(int i=1;i<=m;i++){
int aa,b,c,d;
aa=read(),b=read(),c=read();
if(aa==1){
d=read();
modify(1,1,n,b,c,d,1);
}else if(aa==2){
d=read();
modify(1,1,n,b,c,d,2);
}else{
cout<<query(1,1,n,b,c)<<endl;
}
}
return 0;
}
5.P4513 小白逛公园
题意:单点修改,区间求最大子段和。最大字段和的维护,记录从左开始的子段和和从右开始的子段和,还有ans。
#include<bits/stdc++.h>
#define N 2000001
using namespace std;
int n,m;
struct node{
int l,r;
long long maxleft,maxright,sum,ans;
}tree[N];
void putin(int k){
tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;
tree[k].maxleft=max(tree[k<<1].maxleft,tree[k<<1|1].maxleft+tree[k<<1].sum);//更新左端
tree[k].maxright=max(tree[k<<1|1].maxright,tree[k<<1|1].sum+tree[k<<1].maxright);//右端
tree[k].ans=max(max(tree[k<<1].ans,tree[k<<1|1].ans),tree[k<<1].maxright+tree[k<<1|1].maxleft);//ans
}
void build(int rt,int l,int r){
tree[rt].l=l;
tree[rt].r=r;
if(l==r){
scanf("%lld",&tree[rt].sum);
tree[rt].ans=tree[rt].maxleft=tree[rt].maxright=tree[rt].sum;
return ;
}
int mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
putin(rt);
}
node ask(int k,int x,int y){
if(x<=tree[k].l&&tree[k].r<=y){
return tree[k];
}
int mid=(tree[k].l+tree[k].r)/2;
if(y<=mid) return ask(k<<1,x,y);//只在左
else {
if(x>mid) return ask(k<<1|1,x,y);//只在右
else{//两边都有要合并
node t,a=ask(k<<1,x,y),b=ask(k<<1|1,x,y);
t.maxleft=max(a.maxleft,a.sum+b.maxleft);//更新左右找到答案
t.maxright=max(b.maxright,a.maxright+b.sum);
t.ans=max(max(a.ans,b.ans),a.maxright+b.maxleft);
return t;
}
}
}
void change(int k,int x,int y){
if(tree[k].l==tree[k].r){
tree[k].ans=tree[k].maxleft=tree[k].maxright=tree[k].sum=y;
return ;
}
int mid=(tree[k].l+tree[k].r)/2;
if(x<=mid)change(k<<1,x,y);
else change(k<<1|1,x,y);
putin(k);
}
signed main(){
scanf("%d%d",&n,&m);
build(1,1,n);
while(m--){
int op,x,y;
cin>>op>>x>>y;
if(op==1){
if(x>y) swap(x,y);
cout<<ask(1,x,y).ans<<endl;
}else{
change(1,x,y);
}
}
return 0;
}
6.P1471 方差
区间加,区间平均数,区间方差;
维护两个线段树,一个平方;
#include<bits/stdc++.h>
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define maxn 300010
using namespace std;
double sega[maxn],segb[maxn];
double mark[maxn];
void pushup(int x){
sega[x]=sega[x<<1]+sega[x<<1|1];
segb[x]=segb[x<<1]+segb[x<<1|1];
}
void pushdown(int rt,int x){
if(mark[rt]){
segb[rt<<1]+=2*mark[rt]*sega[rt<<1]+(x-x/2)*mark[rt]*mark[rt];//化简平方和公式
segb[rt<<1|1]+=2*mark[rt]*sega[rt<<1|1]+(x/2)*mark[rt]*mark[rt];
sega[rt<<1]+=(x-x/2)*(mark[rt]);
sega[rt<<1|1]+=(x/2)*(mark[rt]);
mark[rt<<1]+=mark[rt];
mark[rt<<1|1]+=mark[rt];
mark[rt]=0;
}
}
void build(int rt,int l,int r){
if(l==r){
cin>>sega[rt],segb[rt]=sega[rt]*sega[rt];
}else{
int mid=l+r>>1;
build(lson);
build(rson);
pushup(rt);
}
}
double query_a(int rt,int l,int r,int L,int R){
if(l>=L&&r<=R){
return sega[rt];
}else{
pushdown(rt,r-l+1);
int mid=l+r>>1;
double ret=0;
if(mid>=L) ret+=query_a(lson,L,R);
if(mid<R) ret+=query_a(rson,L,R);
return ret;
}
}
double query_b(int rt,int l,int r,int L,int R){
if(l>=L&&r<=R){
return segb[rt];
}else{
pushdown(rt,r-l+1);
int mid=l+r>>1;
double ret=0;
if(mid>=L) ret+=query_b(lson,L,R);
if(mid<R) ret+=query_b(rson,L,R);
return ret;
}
}
void update(int rt,int l,int r,int L,int R,double x){
if(l>=L&&r<=R){
mark[rt]+=x,segb[rt]+=2*x*sega[rt]+x*x*(r-l+1),sega[rt]+=x*(r-l+1);//(a+b)^2=a^2+b^2+2*a*b
}else{
pushdown(rt,r-l+1);
int mid=l+r>>1;
if(mid>=L) update(lson,L,R,x);
if(mid<R) update(rson,L,R,x);
pushup(rt);
}
}
double sqr(double x){
return x*x;
}
int main(){
int n,m,x,y,c;
double z;
scanf("%d %d",&n,&m);
build(1,1,n);
for(int i=1;i<=m;i++){
scanf("%d",&c);
if(c==2){
scanf("%d%d",&x,&y),printf("%.4lf\n",query_a(1,1,n,x,y)/(y-x+1));
}
if(c==1){
scanf("%d%d%lf",&x,&y,&z);
update(1,1,n,x,y,z);
}
if(c==3){
scanf("%d%d",&x,&y);
double sum1=query_a(1,1,n,x,y);
double sum2=query_b(1,1,n,x,y);
double avi=sum1/(y-x+1);
//cout<<avi<<endl;
printf("%.4lf\n",(sum2-2*sum1*avi+(y-x+1)*avi*avi)/(y-x+1));
}
}
}