51nod1081 子段求和(dp、树状数组、线段树求法)

先上题目:链接http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1081

分析:

如果每次查询都需要在遍历计算的话,即复杂度0(n),肯定超时。

那么我们自然会想到,能不能先算好sum[x](a1+a2+....+ax).那么每次遍历我们只需要O(1)复杂度。

sum[x]也是非常容易求的。。。遍历输入的时候 sum[i]=sum[i-1]+a  (也算是一道小dp)

这样的话,程序就能动手写了。。

但是还有一些其他的数据结构可以解决这一类问题。

树状数组,线段树。


dp(省时省力)

#include <stdio.h>
#include <stdlib.h>
using namespace std;
const int N=50000+5;
long long sum[N];  //记录每一项前面的和 
int main()
{
	int a,n;
	sum[0]=0; 
	scanf("%d",&n);
    for(int i=1;i<=n;i++)  //输出并初始化数据 
    {
    	scanf("%d",&a);
    	sum[i]=sum[i-1]+a;  
	}
	int q,m,l;
	scanf("%d",&q);
	while(q--)
	{
	    scanf("%d%d",&m,&l);
	    printf("%lld\n",sum[m+l-1]-sum[m-1]);
	}
	return 0;
}


树状数组

如果还不清楚树状数组,先学学,很好用的一种数据结构

http://blog.youkuaiyun.com/int64ago/article/details/7429868


学完之后,相信一下程序相当容易了

#include"cstdio"   //树状数组 
#include"cstdlib"
#include"cstring"
#include"algorithm"
#include"iostream"
using namespace std;
const int N=50000+5;
int a[N];
long long c[N];
int n;
int lowbit(int x)  //一分为二的划分 
{
	return (x&-x);  
}
long long  sum(int x) //计算(1-x)区间的和 
{
   	long long  ans=0;
   	while(x>0)  //向左往上遍历 
   	{
   		ans+=c[x];
   		x-=lowbit(x);//遍历左上 
	}
	return ans; 
}
void  add(int x,int d)  //让a[x]增加d  
{
   	 while(x<=n)  //向右往上增加 
   	{
   	 	c[x]+=d;
		x+=lowbit(x);  //往右上遍历 
	}
} 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)  //输出并初始化数据 
    {
    	scanf("%d",&a[i]);
    	add(i,a[i]);
	}
	int q,m,l;
	scanf("%d",&q);
	while(q--)
	{
	    scanf("%d%d",&m,&l);
	    printf("%lld\n",sum(m+l-1)-sum(m-1));
	}
	return 0;
}

线段树

#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#include"algorithm"
#include"iostream"
#include"math.h"
#include"ctype.h"
const int maxn=50005;
//using namespace std;
long long a[maxn];
long long sum[2*maxn+5];
int n;//数组长度 
void Input()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	   scanf("%lld",a+i);
}
void Build(int node,int start,int end)
{//分治法建立线段树  node是线段树起点 [start,end]是数据范围 
	if(start==end)
	{
	   sum[node]=a[start];
	   return ;
    }
	int m=start+(end-start)/2;
	Build(node*2,start,m);     //左子树 
	Build(node*2+1,m+1,end);   //右子树
	sum[node]=sum[node*2]+sum[node*2+1]; 
}
long long  Query(int node,int start,int end,int le,int ri)
{//在区间 [start,end]查找区间[r,l]和(sum),起点node
	if(le<=start&&end<=ri)  //区间覆盖了 直接选取[start,end]
	     return sum[node];
	//system("pause");   
	int m=start+(end-start)/2;
	long long L=0,R=0;
	if(m>=le)   L=Query(node*2,start,m,le,ri); 
	if(m<ri)    R=Query(node*2+1,m+1,end,le,ri);
	return L+R;
}
void Update(int node,int start,int end,int p,int v)
{ 
  //更新a[p]=v
  if(start==end)  //只有一个节点
      sum[node]=v;
  int m=start+(end-start)/2;
  if(p<=m)
     Update(node*2,start,m,p,v);
  else
    Update(node*2+1,m+1,end,p,v);
  sum[node]=sum[node*2]+sum[node*2+1];	
}
void Solve()
{
	Build(1,0,n-1);
	//for(int i=1;i<=2*n;i++)
	//   printf("%lld ",sum[i]);
//	printf("\n");
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int le,len;
		scanf("%d%d",&le,&len);
		long long ans=Query(1,1,n,le,le+len-1);
		printf("%lld\n",ans);
	}
}
int main()
{
	 Input();
	 Solve();
	return 0;
}

如有不妥,还望指出。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值