HDU 3450 Counting Sequences(树状数组+离散化+二分+dp思想 详细解答)

题目链接:HDU 3450

题意:给出一段序列,让你找出一段子序列(长度>=2)满足序列中每相邻的两个数之间的差 <=d,求出这样的序列的个数。

 思路:本题利用dp思想,dp[i] 表示 以 i 为结尾的子序列个数(主要是记录)

以1 3 7 5 为例:

1                      2              3              4              5             6             7

1      

先将1加入,计算dp[3]=val[2]+dp[2]+val[1]+dp[1]+val[4]+dp[4]+val[5]+dp[5](val 代表当前的值,长度d=2,区间为[3-2,3+2])

结束再将3加入

1                      2           3             4             5             6              7

1 1   1

比如加入5时,dp[5]=val[3]+dp[3]+val[7]=3;

但是本题因为没有数据范围,需要离散化处理,处理起来比较麻烦。我们考虑把val[i]和dp[i]合并成新的dp[i],

因此计算区间[n-d,n+d]时只需要计算区间上 dp的和,此时考虑使用树状数组来求和。

代码中详细解释: 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100000+10;
const int mod=9901;
int a[maxn],dp[maxn],b[maxn],sum[maxn];
int n;
int lower_bit(int x)
{
	return x&(-x);
}
void update(int pos,int val)
{
	while(pos<=n)
	{
		sum[pos]+=val;
		pos+=lower_bit(pos);
	}
}
int query(int pos)
{
	int ans=0;
	while(pos>0)
	{
		ans+=sum[pos];
		pos-=lower_bit(pos);
	}
	return ans;
}
int main()
{
	int d;
	while(~scanf("%d%d",&n,&d))
	{
		memset(sum,0,sizeof(sum));
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			b[i]=a[i];
		 } 
		sort(b+1,b+n+1);
		unique(b+1,b+n+1);//对b 数组 去重 
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			//利用lower_bound进行离散化 
			int pos1=upper_bound(b+1,b+n+1,a[i]+d)-(b+1);//找到右界 
			int pos2=lower_bound(b+1,b+n+1,a[i]-d)-(b);//找到左界 
			int temp=lower_bound(b+1,b+n+1,a[i])-b;//当前元素位置 
			int sum=((query(pos1)-query(pos2-1))%mod+mod)%mod;//求和,sum相当于dp[i] 记录当前区间【n-d,n+d】 上符合的序列个数 
			ans=(ans+sum)%mod;//求出所有dp【i】的和 
			update(temp,sum+1);//sum+1 ,就是将dp[i]与val[i] 合并 
		}
		printf("%d\n",ans);
	}
	return 0;
}

 代码中对pos1使用upper_bound考虑样例:1 3 7 5,在对5+2查找时找到位置5,-1处理对应7所在位置4,对应区间[3,7]符合题意;1 3 8 5,对5+2查找到位置5(对应数字8),数字8不包括在区间内,-1处理满足题意,算是个小技巧吧!

总结:本题利用记忆化的思想,在解题过程中引入树状数组来求区间和,简化了过程,在树状数组中值和记录合在一起,方便查找。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值