POJ 3468(成段更新)

Language:
线段树的成段更新
Time Limit: 5000MS Memory Limit: 131072K
Total Submissions: 38010 Accepted: 11017
Case Time Limit: 2000MS

Description

对于数列 A1A2, ... , AN. 你要进行2个操作:将一个区间的数同加上某个数,输出一段区间的和。

Input

第一行2个整数表示数列长度和操作次数. 1 ≤ N,Q ≤ 100000.
第二行为数列 A1A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
接下来的Q行操作:
"C a b c" 表示将 AaAa+1, ... , Ab.加上c. -10000 ≤ c ≤ 10000.
"Q a b" 输出区间[a,b]的和。

Output

输出所有询问的答案,每行1个。

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

Hint

longint会爆的。

Source



显然这题是线段树。

但是我之前习惯的nowl和nowr似乎太费解了(以致于我自己都要想半天)

回去看看其它人咋写……


Program poj3468;
const
   maxn=100000;
   maxq=100000;
var
   n,m,i,j,k:longint;
   lazy,t:array[1..maxn*8] of int64;
   c:char;
Procedure pushup(root:longint);
begin
   t[root]:=t[root*2]+t[root*2+1];
end;
Procedure build(l,r,root:longint);
var
   m:longint;
begin
   if (l=r) then
   begin
      read(t[root]);
      exit;
   end;
   m:=(l+r) shr 1;
   build(l,m,root*2);
   build(m+1,r,root*2+1);
   pushup(root);
end;
Procedure Pushdown(l,r,root:longint);
var
   m:longint;
begin
   m:=(l+r) shr 1;
   if (lazy[root]=0) then exit;
   inc(lazy[root shl 1],lazy[root]);
   inc(lazy[root shl 1+1],lazy[root]);
   inc(t[root shl 1],lazy[root]*(m-l+1));
   inc(t[root shl 1+1],lazy[root]*(r-(m+1)+1));

   lazy[root]:=0;
end;
Procedure update(l,r,nowl,nowr,root,cost:longint);
var
   m:longint;
begin
   if (l<=nowl) and (nowr<=r) then
   begin
      inc(lazy[root],cost);
      inc(t[root],(nowr-nowl+1)*cost);
      exit;
   end;
   pushdown(nowl,nowr,root);
   m:=(nowl+nowr) shr 1;
   if (l<=m) then update(l,r,nowl,m,root*2,cost);
   if (m+1<=r) then update(l,r,m+1,nowr,root*2+1,cost);
   pushup(root);
end;
function query(l,r,nowl,nowr,root:longint):int64;
var
   m:longint;
   res:int64;
begin
   if (l<=nowl) and (nowr<=r) then
   begin
      exit(t[root]);
   end;
   pushdown(nowl,nowr,root);
   m:=(nowl+nowr) shr 1;
   res:=0;
   if (l<=m) then res:=res+query(l,r,nowl,m,root*2);
   if (m+1<=r) then res:=res+query(l,r,m+1,nowr,root*2+1);
   exit(res);
end;


begin
   readln(n,m);
   fillchar(lazy,sizeof(lazy),0);
   build(1,n,1);
   readln;
   while (m>0) do
   begin
      read(c);
      if c='Q' then
      begin
         readln(i,j);
         writeln(query(i,j,1,n,1));
      end
      else
      begin
         readln(i,j,k);
         update(i,j,1,n,1,k);
      end;



      dec(m);
   end;
end.

接下来再来说说zkw线段树的解法:

这涉及到原数组A,差分数组A‘,差分数组前缀前缀和A'':

首先,它们3者满足如下关系:

1.差分数组的前缀和SA[i]'为原数组A[i](定义)。
2.原数组的前缀和SA[I]为差分数组的前缀前缀和A''[i];


接下来我们分别建立A‘和(i*A')的{单点修改,区间求和}线段树
显然要想知道

A[i]+A[i+1]+....A[ j ]
     =SA[ j ]-SA[ i -1]
     =A''[ j  ] - A'' [ i-1 ]
     =(SA'[1] +SA'[2]+...SA''[ j ] )- (SA'[1] +SA'[2]+...SA''[ i-1 ] )
     ={ j *A'[1] +(j-1)'A'[2]+....A'[ j ]}-{ (i-1) *A'[1] +(i-2)'A'[2]+....A'[ i-1 ]  }
     ={  (j+1)*(A'[1]+A'[2]+...A'[ j ] )-(1*A'[1]+2*A'[2]+...j*A'[ j ]  }  -  {   i*(A'[1]+A'[2]+...A'[ i-1 ] )-(1*A'[1]+2*A'[2]+...(i-1)*A'[ i-1 ]  }

这个方程如果看起来乱的话就直接看上面的图A''(相当于原数组前缀和SA-请想办法表示成A‘的累加形式)

显然如果将一个区间[A,B]+V,对3个数组影响如下:


幸运的是,我们只要维护A‘和{i*A’}
A和A‘’都要折腾一段区间,只有A'只要修改头和(尾+1)(如果存在)

那么{i*A‘} 的 维护 也只要修改2个节点 (请注意每个A‘ 实际上是独立的),同上:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<iostream>
#include<functional>
#include<algorithm>
using namespace std;
#define MAXN (100000+10)
#define MAXAi (1000000000)
#define MAXCi (10000)
__int64 a[MAXN];
int n,q;
char c[10];
class SegMentTree
{
public:
	int M;
	__int64 t[MAXN*10];
	void fillchar()
	{
		M=1;while (M-2<n) M<<=1;
		memset(t,0,sizeof(t));
	}
	__int64 h(__int64 x,__int64 y)
	{
		return x+y;
	}
	void update(int x,__int64 c)
	{
		for (x>>=1;x;x>>=1) t[x]=t[x]+c;
	}
	void insert(int x,__int64 c)
	{
		x+=M;
		t[x]+=c;
		update(x,c);
	}
	__int64 find(int l,int r)
	{
		l=l-1+M;r=r+1+M;
		__int64 ans=0;
		while (l^r^1)
		{
			if (~l&1) {ans+=t[l+1];/* cout<<l+1<<' ';*/}
			if (r&1)  {ans+=t[r-1];/* cout<<r-1<<' ';*/}
			l>>=1;r>>=1;
		}
//		cout<<ans<<' ';
		return ans;
	}
	/*
	void print()
	{
		for (int i=1;i<=M*2;i++) if (t[i]!=0) cout<<i<<':'<<t[i]<<' ';
		cout<<endl;
	}
	*/
		
}t_ai,t_iai;
int main()
{
//	freopen("Poj3468.in","r",stdin);
	scanf("%d%d",&n,&q);t_ai.fillchar();t_iai.fillchar();
	a[0]=0;for (int i=1;i<=n;i++) scanf("%I64d",&a[i]);
	for (int i=1;i<=n;i++)
	{
		t_ai.insert(i,a[i]-a[i-1]);
		t_iai.insert(i,i*(a[i]-a[i-1]));
	}	
	for (int i=1;i<=q;i++)
	{
		scanf("%s",c);
		if (c[0]=='Q')
		{
			int p1,p2;
			scanf("%d%d",&p1,&p2);p1--;
			__int64 ans1=(p1>0)?(__int64)(t_ai.find(1,p1)*(__int64)((__int64)p1+1)-(__int64)t_iai.find(1,p1)):0;//cout<<ans1<<endl;
			__int64 ans2=(__int64)t_ai.find(1,p2)*(__int64)(p2+1)-(__int64)t_iai.find(1,p2);//cout<<ans2<<endl;
			cout<<(__int64)(ans2-ans1)<<endl;
		}
		else
		{
			int l,r;
			__int64 c;
			scanf("%d%d%I64d",&l,&r,&c);
			t_ai.insert(l,(__int64)c);
			t_iai.insert(l,(__int64)c*l);
			if (r<n) 
			{
				t_ai.insert(r+1,-(__int64)c);
				t_iai.insert(r+1,-(__int64)c*(__int64)(r+1));
			}
		}
//		t_ai.print();
//		t_iai.print();
	}

	
	return 0;
}











  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值