1. 程式人生 > >HDU 3450 Counting Sequences(樹狀陣列+離散化+二分+dp思想 詳細解答)

HDU 3450 Counting Sequences(樹狀陣列+離散化+二分+dp思想 詳細解答)

題意:給出一段序列,讓你找出一段子序列(長度>=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處理滿足題意,算是個小技巧吧!

總結:本題利用記憶化的思想,在解題過程中引入樹狀陣列來求區間和,簡化了過程,在樹狀陣列中值和記錄合在一起,方便查詢。