【HDU5542 2015 CCPC 南阳国赛C】【DP】The Battle of Chibi n个数中恰好长度为m的单升子序列数

来源:互联网 发布:详情页美工 编辑:程序博客网 时间:2024/06/06 10:02
#include<stdio.h>#include<string.h>#include<algorithm>using namespace std;const int Z=1e9+7;const int N=1005;int casenum,casei;int n,m;int a[N];int f[N][N];int d[N][N];int top;#include<map>map<int,int>mop;map<int,int>::iterator it;inline void add(int &x,int y){x+=y;if(x>=Z)x-=Z;}int cnt(int p,int x){long long tmp=0;for(;x;x-=x&-x)tmp+=d[p][x];return tmp%Z;}void add(int p,int x,int v){for(;x<=top;x+=x&-x)add(d[p][x],v);}int main(){scanf("%d",&casenum);for(casei=1;casei<=casenum;casei++){scanf("%d%d",&n,&m);mop.clear();for(int i=1;i<=n;i++)scanf("%d",&a[i]),mop[a[i]]=0;top=0;for(it=mop.begin();it!=mop.end();it++)it->second=++top;for(int i=1;i<=n;i++)a[i]=mop[a[i]];memset(d,0,(m+1)*N*4);memset(f,0,(n+1)*N*4);int ans=0;for(int i=1;i<=n;i++){int top=min(i,m);for(int j=1;j<=top;j++){if(j==1)f[i][j]=1;else add(f[i][j],cnt(j-1,a[i]-1));add(j,a[i],f[i][j]);}add(ans,f[i][m]);}printf("Case #%d: %d\n",casei,ans);}return 0;}/*【题意】给你n(1e3)个数,每个数都在[1,1e9]范围。然后让你保持数的顺序不变,选出长度恰好为m(1<=m<=n)的单调上升子序列。问你有多少种选择方案【类型】DP最长上升子序列离散化+树状数组【分析】这道题一开始我是以O(nlogn)最长上升子序列的插入法做。但是很难实现。卡了一段时间。后来结合n只有1000的情况,我们应该设想到O(n^2)的做法。题目涉及到选择,我们不妨回归到一个很经典的DP模型——用f[i][j]表示前i个数中选择了j个数的上升子序列的方案数。这个状态的一个好处是,它把这个上升自序列的长度也包含了,就是j。还有一个性质,我们如何查看一个数能否接在前面的序列后?其实只要知道之前上升子序列的最后一位就可以进行判定了。鉴于这个,我们修改下f[i][j]的含义——f[i][j]表示前i个数中选择了j个数,且最后一个数严格为a[i]时的上升子序列方案数。这样就有状态转移f[i][j]=∑f[p][j-1],p∈[1,i-1]且a[p]<a[i]因为我们可以严格保持i的升序,所以就只需要找到所有长度为j-1且尾节点a[p]<a[i]的子序列个数即可。且因为for i for j已经使得复杂度变成O(n^2),所以这个操作要在log(n)级别的时间内完成。而且这个操作设计到动态操作,于是我们想到树状数组(线段树也可)。这样这道题的做法就很清晰了。1,离散化2,状态转移for(int i=1;i<=n;i++){//其实这个j的顺序并不重要,因为我们更新的尾节点长度为a[i],自己是无法延伸而来的for(int j=top;j>=1;j--){if(j==1)f[i][j]=1;else add(f[i][j],cnt(j-1,a[i]-1));add(j,a[i],f[i][j]);}add(ans,f[i][m]);}【时间复杂度&&优化】O(n^2 logn)取模还是很消耗时间的233*/

1 0
原创粉丝点击