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
",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
",ans);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/lienus/p/4128772.html