URAL1523(dp+树状数组)

 

题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=41224#problem/B

分析:可以设dp[i][j]表示以i结尾长度为j的子序列的个数,那么更新就是dp[i][j]=∑dp[k][j-1],其中k<i,而且a[k]>a[i]。而要更新dp值,可以用树状数组维护,按顺序插入序列值,那么树状数组的值就可以表示比它小的长度为j-1的所有子序列的和,这样就可以在logn的时间更新dp值了,所以总复杂度是O(n*k*logn)

树状数组有两种用途(以一维树状数组举例):   

1.单点更新,区间查询(即求和)         

单点更新时,是往树根(即c[n])拓展         

而区间查询时,是往叶子节点(即c[1])拓展   

2.区间更新,单点查询         

区间更新时,是往叶子节点(即c[1])拓展         

单点查询时,往树根(即c[n])拓展

这两个操作只不过是在update()和sum()方法中的+和-替换一下而已。

 

下面解释一下“区间更新时,是往叶子节点(即c[1])拓展” 和 “单点查询时,往树根(即c[n])拓展” 的原因

举例说明吧:

c[1]=a[1]; 

c[2]=a[1]+a[2];

c[3]=a[3];

c[4]=a[1]+a[2]+a[3]+a[4];

c[5]=a[5];

c[6]=a[5]+a[6];

c[7]=a[7];

c[8]=a[1]+...+a[8];

假如我要更新区间[3,7],那么我首先更新[1,7],即该区间+1;再更新[1,2],该区间-1:

由于更新时往叶子节点拓展的,所以更新[1,7]时:c[7]++,c[6]++,c[4]++。

可以发现,这三个正好包含了a[1]~a[7]一次,相当于a[1]~a[7]都更新一遍 而更新[1,2]时:c[2]--,包含了a[1],a[2]。

那么当我查询某一值a[i],由于是往根节点拓展,所以每个包含a[i]的c[j]都会遇到一次(更新时每个a[i]变化一次)

即之前凡是更新过的值我都会加上。

如我想查询a[2],那么a[2]=c[2]+c[4]+c[8]=-1+1+0=0;

查询a[3],那么a[3]=c[3]+c[4]+c[8]=0+1+0=1。

 

1)树状数组区间更新,单点求值方法

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define LL long long

#define M 1000000000

#define maxn 22222

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

using namespace std;

int a[maxn],c[maxn];

int dp[maxn][12];//dp[i][j]表示以a[i]为结尾长度为j的子序列;dp[i][j]=∑dp[k][j-1]且a[i]>a[k]

int n,k;

int lowbit(int x)

{

    return x&(-x);

}

int sum(int i)

{

    LL res=0;

    while(i<=n)

    {

        res=(res+c[i])%M;

        i+=lowbit(i);

    }

    return res;

}

void update(int i,int x)

{

    while(i)

    {

        c[i]=(c[i]+x)%M;

        i-=lowbit(i);

    }

}

int main()

{

   while(scanf("%d%d",&n,&k)>0)

   {

       memset(dp,0,sizeof(dp));

       for(int i=1;i<=n;i++)

       {

           scanf("%d",&a[i]);

           dp[a[i]][1]=1;

       }

       for(int i=2;i<=k;i++)

       {

           memset(c,0,sizeof(c));

           for(int j=i-1;j<=n;j++)

           {

               dp[a[j]][i]=sum(a[j]);//求长度为i以a[j]点为结束的逆序对数值

               update(a[j],dp[a[j]][i-1]);//更新处在0~a[j]的数逆序对+dp[a[j]][i-1]

           }

       }

       LL ans=0;

       for(int i=1;i<=n;i++)

       {

           ans=(ans+dp[a[i]][k])%M;

       }

       printf("%lld\n",ans);

   }

}
View Code

2)树状数组单点更新,区间求值方法

#include<iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

#define LL long long

#define M 1000000000

#define maxn 22222

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

using namespace std;

int a[maxn],c[maxn];

int dp[maxn][12];//dp[i][j]表示以a[i]为结尾长度为j的子序列;dp[i][j]=∑dp[k][j-1]且a[i]>a[k]

int n,k;

int lowbit(int x)

{

    return x&(-x);

}

void update(int x,int y)

{

    while(x<=n)

    {

        c[x]=(c[x]+y)%M;

        x+=lowbit(x);

    }

}

int sum(int x)

{

    int res=0;

    while(x)

    {

        res=(res+c[x])%M;

        x-=lowbit(x);

    }

    return res;

}

int main()

{

    while(scanf("%d%d",&n,&k)>0)

    {

        for(int i=1;i<=n;i++)

        {

            scanf("%d",&a[i]);

            dp[i][1]=1;

        }

        int res;

        for(int i=1,j=n;i<j;i++,j--) res=a[i],a[i]=a[j],a[j]=res;

        for(int i=2;i<=k;i++)

        {

            memset(c,0,sizeof(c));

            for(int j=i-1;j<=n;j++)

            {

                dp[a[j]][i]=sum(a[j]);//求在0~a[j]比a[j]小的所在点逆序对之和

                update(a[j],dp[a[j]][i-1]);//更新a[j]点的逆序对

            }

        }

        int ans=0;

        for(int i=1;i<=n;i++) ans=(ans+dp[a[i]][k])%M;

        printf("%d\n",ans);

    }

    return 0;

}
View Code

 

你可能感兴趣的:(树状数组)