题意:对于一个N个数的序列求长度为M的上升子序列总数(结果取%)
1<=N<=1000;1<=M<=N;1<=ai<=109.共1~100组数据,4000MS
解题思路:
考虑使用DP,先写出最朴素形式,DP[i][j][k]表示已判断到第i个数,序列长度为j,前一个数大小为k(已离散化)时方案数。复杂度为O(N*M*N),直接上显然会T
优化方法较为明显,维护n个树状数组S[i],S[j][k]表示到当前为止,最大数小于k且长度为j的序列总数,则每次对S[j](j从min(i,m)到1)进行操作:add(a[i]+1,tmp),其中tmp=ask(j-1,a[i]),则每次转移结束,S[j][k]保存从1到i的所有可能状态,复杂度O(NMlogN)
注意点:S数组转移时循环变量应采用倒序
#include<iostream> #include<cstring> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int mo=1000000007; const int maxn=1001; int t,n,m,now,maxs,tmp,ans,gd,wz; int s[2000][maxn+5]; int jl[2000],ys[2000]; int c[2000]; int lowbit(int x){return (x&(-x));} void add(int u,int x,int value){ for (int i=x;i<=maxs+1;i+=lowbit(i)){ s[u][i]=(s[u][i]+value)%mo; } } int ask(int u,int x){ { int sum=0; for (int i=x;i;i-=lowbit(i)) sum=(sum+s[u][i])%mo; return sum; } } bool cmd(int a,int b){ return (jl[a]<jl[b]); } int main(){ freopen("1.in","r",stdin); freopen("1.out1","w",stdout); scanf("%d",&t); for (int q=1;q<=t;++q){ scanf("%d%d",&n,&m);maxs=0; for (int i=1;i<=n;++i) {scanf("%d",&jl[i]);ys[i]=jl[i];} for (int i=1;i<=n;++i) c[i]=i; sort(c+1,c+1+n,cmd); jl[c[1]]=1;wz=0; for (int i=2;i<=n;++i){ if (ys[c[i]]==ys[c[i-1]]) ++wz; jl[c[i]]=i-wz; } maxs=n-wz; memset(s,0,sizeof(s)); add(1,jl[1]+1,1);add(0,1,1); for (int i=2;i<=n;++i){ for (int j=min(i,m);j>=1;--j) if (j>i) break; else { tmp=(ask(j-1,jl[i])) % mo; if (tmp!=0) add(j,jl[i]+1,tmp); } } //cout<<s[0][2]<<endl; int ans=ask(m,maxs+1) % mo; printf("Case #%d: %d\n",q,ans); } return 0; }
时间: 2024-10-22 13:17:47