题目链接: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); } }
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; }