题目链接: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处理满足题意,算是个小技巧吧!
总结:本题利用记忆化的思想,在解题过程中引入树状数组来求区间和,简化了过程,在树状数组中值和记录合在一起,方便查找。