树状数组

                                树状数组

 

很显然是动态规划。dp[i]表示前i个数有多少个有效的子序列。那么 dp[i]=dp[i-1]+A。 A是前面i-1个数中,与i的差值不超过d的以该数结尾的有效的子序列的个数 的和。我们可以用另外一个数组sub[i]表示以i结尾的有效的子序列的个数。 dp与sub的不同之处是dp中的子序列不一定是以第i个数结尾的。
sub[i]= sigma sub[k] ,( abs(numk],num[i])<=d )。 由于求sub的时间复杂度为O(n^2),而n太大,因此需要离散化后用树状数组。
树状数组求和是一段连续的,而sub要求和的是位于区间[num[i]-d,num[i]+d],所以要对num排序,这样就能把
[num[i]-d,num[i]+d]放到连续的区间中。


HDU 3450

#include <iostream>
#include <algorithm>
#include <string>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;

const int MOD = 9901;
const int N = 1e5 + 5;
int n,dp[N],cnt[N],tree[N];
struct Node
{
    int index,value;
    bool operator < (const Node &x)const
    {
        return value < x.value;
    }
}num[N];

static inline int lowbit(int t)
{
    return(t&(-t));
}

void Update(int i,int d)
{
    d %= MOD;
    for(;i <= n;i += lowbit(i)) tree[i] = (tree[i]+d)%MOD;
}

int GetSum(int i)
{
    int s = 0;
    for(;i > 0;i -= lowbit(i)) s = (s+tree[i])%MOD;
    return s;
}

int Find(int k)
{
    int mid,ret = 0,left = 1,right = n;
    while(left <= right)
    {
        mid = (left+right)>>1;
        if(num[mid].value <= k){   // 求上界
           left = mid + 1;
           ret = mid;
        }
        else
           right = mid - 1;
    }
    return ret;
}
int main()
{
    int d;
    while(~scanf("%d%d",&n,&d))
    {
        for(int i = 1;i <= n;++i)
        {
            scanf("%d",&num[i].value);
            num[i].index = i;
        }
        sort(num+1,num+1+n);
        for(int i = 1;i <= n;++i)
        {
            cnt[num[i].index] = i;
        }
        memset(tree,0,sizeof(tree));
        Update(cnt[1],1);                          //预处理第一个点,题目要求至少两个点
        dp[1] = 0;
        for(int i = 2;i <= n;++i)
        {
            int k1 = Find(num[cnt[i]].value+d);    //其中的i,就是原本输入时的索引.而cnt[i]为排序后的位置(离散化后)
            int k2 = Find(num[cnt[i]].value-d-1);
            int tmp = GetSum(k1) - GetSum(k2);
            tmp = (tmp%MOD+MOD)%MOD;
            dp[i] = (dp[i-1]+tmp)%MOD;
            Update(cnt[i],tmp+1);               //cnt[i]位置满足条件的有几个
        }
        printf("%d\n",dp[n]);
    }
    return 0;
}

/*
   因为题目要求的是,按原来序列中的顺序找到子序列。而经过排序后原来的位置将有可能全部打乱。
   此时要怎么办才可以在即运用树状数组的前提下,又可以满足题目的要求呢?
   我们此时就会想到了运用离散化的思想。
   为什么这个可行呢?
   因为,我们对value排序后,虽然索引是打乱了,但是我们之前记住了输入时的位置。
   所以,我们可以根据记录的index来找回输入时的位置。
   刚接触很难理解,我也是如此。下面就来举个例子。
   比如,num[i].index = 2;而排序后的i值可能为i = 4;
   而调用cnt[2] = 4;(表示当输入时下标为2的,经过了排序后位置变为了4)
   而我们在统计的时候,我们还是要根据之前的下标位置来进行。
   还是上面一个例子,当我们要统计第二个输入的数时候,我们怎么知道他在排序后的位置是在哪里
   呢?
   此时我们调用pos = cnt[2],pos即使排序后的位置,然后我们将pos传给num[pos].value就可以了。
   这样我们就证明完了离散化后的正确性。
*/


什么是离散化?

       排序后处理/对坐标的近似处理

       基本思想:只考虑需要用的值。

       作用:有效的降低时间复杂度。


















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值