多种方法求解Pku3468 A Simple Problem with Integers

You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of op
eration is to add some given number to each number in a given interval. The other is to ask for the
sum of numbers in a given interval.
1.给[a ,b]整体上加上一个常数c。
2.查询[a ,b]区间的和。
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
"C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.
Output
You need to answer all Q commands in order. One answer in a line. The sums may exceed the range of 32-bit integers
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15

Sol1:Lazy标记

#include<bits/stdc++.h>
using namespace std;
long long sum[800000],n,q,tt[800000],lazy[800000],size[800000],a1,a2,a3;
char s;
void a_lazy(int p,int v) //打Lazy标记操作
{
	sum[p]+=size[p]*v;  //更新SUM的值,体现加值的操作效果
	lazy[p]+=v;             
}
void push_down(int p)  //下放标记,其与上面那个是两个含义
{
	a_lazy(p*2,lazy[p]);
                a_lazy(p*2+1,lazy[p]);
	lazy[p]=0;
}
void updata(int p)
{
	sum[p]=sum[p*2]+sum[p*2+1]; 
               //用左右子结点的SUM值“更新”父亲点的,注意这里是赋值,而不是累加
               //好比我们对[1..10]加了5,于是标记只打在[1..10]上,并不下传
              //然后对[1..5]加上6,于是加5的标记要传到两个子结点上,然后再将加6的标记传到[1..5】上
              //于是得到[1..5]的权值为5*11=55,[6..10]的权值为5*5=25
              //于是[1..10]的权值为55+25=80
	size[p]=size[p*2]+size[p*2+1]; 
}
void build(int p,int l,int r)
{
	if(l==r)
	{
		sum[p]=tt[l],size[p]=1;
		return;
	}
	int mid=(l+r)/2; 
	build(p*2,l,mid);build(p*2+1,mid+1,r);
	updata(p);
}
void q_add(int p,int l,int r,int x,int y,int v)
{
	if(l>=x&&r<=y)  //只有当线段树上区间为当前操作区间的子集时,才打上lazy标记
	{
		a_lazy(p,v);
		return; 
	}
                //如果没有返回的话,说明线段树上的区间大于当前操作的区间
	int mid=(l+r)/2;
	push_down(p);  //此时要将P上的标记,进行下传
	if(mid>=x)q_add(p*2,l,mid,x,y,v);
	if(mid<y)q_add(p*2+1,mid+1,r,x,y,v);
	updata(p);  //信息及时上传
}
long long query(int p,int l,int r,int x,int y)
{
	if(l>=x&&r<=y) //sum[p]是真实体现这一段数字和的,标记带来的影响已统计进去了
                       return sum[p];
	long long ans=0;
	push_down(p);  
                 //此时也要进行标记下传,因为要统计的区间可能从前没有访问过,所以标记根本没有下传, 其sum值也不是真实的
	int mid=(l+r)/2; 
	if(mid>=x)ans+=query(p*2,l,mid,x,y);
	if(mid<y)ans+=query(p*2+1,mid+1,r,x,y);
	return ans;
}
int main()
{
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++)scanf("%lld",&tt[i]);
	build(1,1,n);
	for(int i=1;i<=q;i++)
	{
		cin>>s;
		if(s=='Q')
		{
			scanf("%lld%lld",&a1,&a2);
			printf("%lld
",query(1,1,n,a1,a2));
		}
		else
		{
			scanf("%lld%lld%lld",&a1,&a2,&a3);
			q_add(1,1,n,a1,a2,a3);
		}
	}
	
    return 0;
}

  

#include<cstdio>
#define mid (s[x].l+s[x].r>>1)
int n,m,d[100001];
char p[3];
struct oo{int l,r;long long lazy,sum;}s[400001];
void build(int x,int l,int r)
{
    s[x].l=l,s[x].r=r;
    if(l==r){s[x].sum=d[l];return ;}
    build(x<<1,l,mid);build(x<<1|1,mid+1,r);
    s[x].sum=s[x<<1].sum+s[x<<1|1].sum;
}
void pushdown(int x)  //lazy标记下传
{
    int l=x<<1,r=x<<1|1;
    s[l].sum+=(s[l].r-s[l].l+1)*s[x].lazy;
    s[r].sum+=(s[r].r-s[r].l+1)*s[x].lazy;
    s[l].lazy+=s[x].lazy;s[r].lazy+=s[x].lazy;
    s[x].lazy=0;
}
void change(int x,int l,int r,int v)
{
    if(l<=s[x].l&&r>=s[x].r)
    {
        s[x].sum+=(s[x].r-s[x].l+1)*v;
        s[x].lazy+=v;
        return ;
    }
    if(s[x].lazy)  //记得标记下传
        pushdown(x);
    if(l<=mid)
        change(x<<1,l,r,v);
    if(r>mid)
        change(x<<1|1,l,r,v);
    s[x].sum=s[x<<1].sum+s[x<<1|1].sum;
}
long long get(int x,int l,int r)
{
    if(l<=s[x].l&&r>=s[x].r)
        return s[x].sum;
    if(s[x].lazy)       //记得标记下传
       pushdown(x);
    long long ans=0;
    if(l<=mid)ans+=get(x<<1,l,r);
    if(r>mid)ans+=get(x<<1|1,l,r);
    s[x].sum=s[x<<1].sum+s[x<<1|1].sum;
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&d[i]);
    build(1,1,n);int x,y,z;
    while(m--)
    {
        scanf("%s",p+1);
        if(p[1]=='Q')
        {
            scanf("%d%d",&x,&y);
            printf("%lld
",get(1,x,y));
        }
        else
        {
            scanf("%d%d%d",&x,&y,&z);
            change(1,x,y,z);
        }
    }
}

  

所谓标记永久化,即只在线段树上的区间为当前操作区间的子集时,
才打上标记,且标记并不下发到子结点。

//设树的结构为 p1
//                      /
//                   p2
//                   /
//               p3
//当我们有个标记打在p2上面时,则从p1开始走,就事先算出对p1的影响
//由于影响已算出来了,即sum[p1]体现的就是这一段真实的数字和
//于是也就用不着updata上传信息了
//然后走到p2,算出对p2的影响,由于并没有接着向下走
//所以事实上是对p3区间是有影响的,但标记没有下传
//于是在算p3的时候,需要累加从p1到p2这一段的标记量的累加

下面这个代码是Pku3468 A Simple Problem with Integers,代码来自zy

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,m;
ll a[maxn];
ll sum[maxn<<2],add[maxn<<2];
void updata(int p)
{
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void build(int p,int l,int r)
{
if(l==r)
{
         sum[p]=a[l];
         return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
updata(p);
}
void change(int p,int l,int r,int L,int R,ll v)
{
sum[p]+=v*(min(R,r)-max(L,l)+1); 
//记录下对区间P的实际贡献 
if(L<=l&&r<=R)
{
        add[p]+=v;
        return;
}
int mid=(l+r)>>1;
if(L<=mid) //如果操作的左边界小于等于mid时,落在左子树 
change(p<<1,l,mid,L,R,v);

if(R>mid) //如果操作的右边界大于mid,落在右子树 
change(p<<1|1,mid+1,r,L,R,v);

}
ll query(int p,int l,int r,int L,int R)
{
if(L<=l&&r<=R)
    return sum[p];
int mid=(l+r)>>1;
ll res=add[p]*(min(R,r)-max(L,l)+1);
//累加一路走来,遇到的标记,注意res是个局部变量
if(L<=mid)
    res+=query(p<<1,l,mid,L,R);
if(R>mid)
    res+=query(p<<1|1,mid+1,r,L,R);

return res;
}
int main() 
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",a+i);
build(1,1,n);
for(int i=1;i<=m;i++) {
int opt,l,r;ll v;
char p[3];
scanf("%s",p+1);
int x,y;scanf("%d%d",&l,&r);
if(p[1]=='C')
{
scanf("%lld",&v);
change(1,1,n,l,r,v);
}
else printf("%lld
",query(1,1,n,l,r));
}
return 0;
}

  这个是xyh的

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
 
const int maxn=1e5+5;
 
int n,m;
ll a[maxn];
 
struct segment_tree 
{
    ll sum[maxn<<2],add[maxn<<2];
    void updata(int p) 
	{
        sum[p]=sum[p<<1]+sum[p<<1|1];
    }
    void build(int p,int l,int r) 
	{
        if(l==r) {sum[p]=a[l];return;}
        int mid=(l+r)>>1;
        build(p<<1,l,mid);
        build(p<<1|1,mid+1,r);
        updata(p);
    }
    void change(int p,int l,int r,int L,int R,ll v) 
	{
        sum[p]+=v*(R-L+1);
        if(L<=l&&r<=R) 
		{
			 add[p]+=v;
			 return;
		}
        int mid=(l+r)>>1;
        if(R<=mid)
		   change(p<<1,l,mid,L,R,v);
        else 
		    if(L>mid)
			   change(p<<1|1,mid+1,r,L,R,v);
            else 
			  change(p<<1,l,mid,L,mid,v),change(p<<1|1,mid+1,r,mid+1,R,v);
    }
 
    ll query(int p,int l,int r,int L,int R) 
	{
        if(L<=l&&r<=R)
		   return sum[p];
        int mid=(l+r)>>1;
		ll res=add[p]*(R-L+1);  //累加一路走来,遇到的标记
        if(R<=mid)
		   res+=query(p<<1,l,mid,L,R);
        else 
		   if(L>mid)
		      res+=query(p<<1|1,mid+1,r,L,R);
           else 
		    res+=query(p<<1,l,mid,L,mid)+query(p<<1|1,mid+1,r,mid+1,R);
        return res;
    }
}T;
 
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",a+i);
    T.build(1,1,n);
    for(int i=1;i<=m;i++) {
        int opt,l,r;ll v;
        char p[3];
      
        scanf("%s",p+1);
        int x,y;scanf("%d%d",&l,&r);
        if(p[1]=='C')
		{
            scanf("%lld",&v);
            T.change(1,1,n,l,r,v);
        }
        else printf("%lld
",T.query(1,1,n,l,r));
    }
    return 0;
}

  

 
 

  下面这个是ljq的

//s[p]代表p这一段的数字和,但凡有标记打在p及p的子结点上,都会体现在s[p]上
//但是在p之上结点打过的标记,则不会体现出来。于是要累加一路走下来的标记
//设树的结构为    p1
//                          /
//                       p2
//                       /
//                    p3
//                     /
//                 p4
//设目标有标记打在p2上,则这个影响量开始只算出在p2上面的,
//即s[p2]的值是真实这一段的数字之和
//然后利用updata上传到p1上
//同理,如果有标记打在p3上,也是开始只算出p3的,然后上传到p2,p1上
//由于修改p2,p3,对p4也是有影响的,但标记并没有下传
//于是在算p4的时候,要累加从p1到p3这一段的标记量之和

//标记不下传,只当线段树上区间是目前操作区间的子集时,才打上标记
//这样对于父区间来说,如果标记只打在子区间上,sum的集合是不完整的,于是要updata
//在统计信息的时候,sum代表的时,所有标记打在这个区间上的
//于是因为标记没下传,于是统计要要加上一路走过来时所要累加的量
//例如对[1..10]打上10,[1..5]打上8
//则统计[1..10]的总和时,直接返回sum[1](加上8的那部分在updata时传上来了) 
//统计[1..5]时,要返回sum[2]+一路走过的标记*5 
#include<cstdio>
#include<iostream>
typedef long long ll;
using namespace std;
ll a[100001];
struct node
{
    #define ls (p<<1)
    #define rs (p<<1|1)
    ll s[400001],cnt[400001];
    void updata(int p,int l,int r)
    {
        s[p]=s[ls]+s[rs]+cnt[p]*(r-l+1);
    }
    void build(int p,int l,int r)
    {
        cnt[p]=0;
        if(l==r)
        {
            s[p]=a[l];return ;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid);
        build(rs,mid+1,r);
        updata(p,l,r);
    }
    void change(int p,int l,int r,int x,int y,int v)
    //l,r是线段树上的区间
	//x,y是当前要操作的 
    {
        if(x<=l&&r<=y) //只有当目前线段树上的区间是要操作的区间的子集时 
        {
            	cnt[p]+=v; //加上标记 
            	s[p]+=1LL*(r-l+1)*v;
             	return ;
        }
        int mid=(l+r)>>1;
        if(x<=mid)change(ls,l,mid,x,y,v);
        if(y>mid)change(rs,mid+1,r,x,y,v);
        updata(p,l,r);
    }
    long long ans;
    void query(int p,int l,int r,int x,int y,int cc)
    {
    //    printf("%d %d %d %d %d %lld %lld
",p,l,r,x,y,s[p],cnt[p]);
        if(x<=l&&r<=y)
		{
	           ans+=s[p];
   	           ans+=1LL*cc*(r-l+1);
	           return;
        }
        int mid=(l+r)>>1;
        if(x<=mid)query(ls,l,mid,x,y,cc+cnt[p]);
        if(y>mid)query(rs,mid+1,r,x,y,cc+cnt[p]);  
    }
}tree;
int main()
{
    int n,q,x,y,z;
    char c;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
      scanf("%lld",&a[i]);
    tree.build(1,1,n);
    for(int i=1;i<=q;i++)
    {
        cin>>c;
        scanf("%d%d",&x,&y);
        if(c=='Q')
		{
	           tree.ans=0;
	           tree.query(1,1,n,x,y,0);
	           printf("%lld
",tree.ans);
        }
		else
		{
			scanf("%d",&z);
			tree.change(1,1,n,x,y,z);}
        }
}

  

#include<iostream>
#include<cstdio>
#include<cstring>
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
#define N 201000
using namespace std;
int n,m;char p[3];
long long sum[N*4],add[N*4];
int a[N];
void build(int l,int r,int rt)
{
    if(l==r)
	{
        sum[rt]=a[l];return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int rt,int l,int r,int v,int xl,int xr)
{
    sum[rt]+=v*(min(xr,r)-max(xl,l)+1);
    if(l>=xl&&r<=xr) //等到区间完全重合时,再给add加上v增加的标记 
	{
        add[rt]+=v; 
		return;
    }
    int mid=(l+r)>>1;
    if(xr<=mid)  
	   update(rt<<1,l,mid,v,xl,xr);
    else
	{
        if(xl>mid)   
		   update(rt<<1|1,mid+1,r,v,xl,xr);
        else 
		   update(rt<<1,l,mid,v,xl,xr),update(rt<<1|1,mid+1,r,v,xl,xr);
		  
    }
}
long long query(int rt,int ad,int l,int r,int xl,int xr)
{
    if(xl<=l&&xr>=r)
	{
        return sum[rt]+ad*(min(xr,r)-max(xl,l)+1);
    }  
    int mid=(l+r)>>1;
    if(xr<=mid) 
	   return query(rt<<1,ad+add[rt],l,mid,xl,xr);
    else
	{
     if(xl>mid) 
	  return query(rt<<1|1,ad+add[rt],mid+1,r,xl,xr);
     else 
		return query(rt<<1,ad+add[rt],l,mid,xl,xr)+query(rt<<1|1,ad+add[rt],mid+1,r,xl,xr);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    pos(i,1,n) scanf("%d",&a[i]);
    build(1,n,1);
    pos(i,1,m)
	{
        scanf("%s",p+1);
        int x,y;scanf("%d%d",&x,&y);
        if(p[1]=='C')
		{
            int k;scanf("%d",&k);
            update(1,1,n,k,x,y);
        }
        else printf("%lld
",query(1,0,1,n,x,y));
    }
    return 0;
}

使用动态开点解决上述问题

题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例#1:
11
8
20
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
(数据已经过加强_,保证在int64/long long数据范围内)

#include<cstdio>
#include<iostream>
using namespace std;
const int N=300010;
struct node{
	int ls,rs,lazy;
	long long sum;
}tr[N];
int root=0,cnt=0;
void insert(int &root,int l,int r,int ll,int rr,int x)
{
	if(!root) //动态开点 
	    root=++cnt;
	int b=min(r,rr)-max(l,ll)+1;
	tr[root].sum+=b*x;
	if(l>=ll&&r<=rr)
	{
		tr[root].lazy+=x;
		return;
	}
	int mid=l+r>>1;
	if(ll<=mid)
	   insert(tr[root].ls,l,mid,ll,rr,x);
	if(rr>mid)
	   insert(tr[root].rs,mid+1,r,ll,rr,x);
} 
long long query(int root,int l,int r,int ll,int rr)
{
	if(l>=ll&&r<=rr)
	   return tr[root].sum;
	int mid=l+r>>1;
	if(tr[root].lazy) //lazy标记下传 
	{
		if(!tr[root].ls)
		   tr[root].ls=++cnt;
		tr[tr[root].ls].lazy+=tr[root].lazy;
		tr[tr[root].ls].sum+=tr[root].lazy*(mid-l+1);
		if(!tr[root].rs)
		   tr[root].rs=++cnt;
		tr[tr[root].rs].lazy+=tr[root].lazy;
		tr[tr[root].rs].sum+=tr[root].lazy*(r-mid);
		tr[root].lazy=0;
	}
	long long ans=0;
	if(ll<=mid)
	   ans+=query(tr[root].ls,l,mid,ll,rr);
	if(rr>mid)
	   ans+=query(tr[root].rs,mid+1,r,ll,rr);
	return ans;
}
int main()
{
	int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
	{
        int temp;
        cin>>temp;
        insert(root,1,n,i,i,temp);
    }
    for(int i=1;i<=m;i++)
	{
        int ta,tb,tc,td;
        cin>>ta>>tb>>tc;
        if(ta==1)
		{
            cin>>td;
            insert(root,1,n,tb,tc,td);
        }
		else 
		if(ta==2)
		{
            cout<<query(root,1,n,tb,tc)<<endl;
        }
    }
    return 0;
}

  

原文地址:https://www.cnblogs.com/cutemush/p/13289075.html