BZOJ 4361: isn
见计数想容斥
考虑先求出 $F[i]$ 表示每种长度的不下降子序列的方案数,但是可能有多算,因为这样有算 从长度 $i+1$ 的不下降子序列变成长度为 $i$ 的不下降子序列的情况
而根据题目的要求一旦序列不下降就要停止操作,但是可以发现 $F[i]$ 只要扣掉 $F[i+1]*(i+1)$ 就行了
现在考虑一下怎么求 $F[i]$,看到 $n<=2000$,考虑 $dp$,设 $f[i][j]$ 表示以第 $i$ 个数为结尾,长度为 $j$ 的不降子序列方案数
那么枚举前 $i$ 个数所有小于第 $i$ 个数的值 $A[i]$ 的位置 $k$,$f[i][j]+=f[k][j-1]$
这个转移显然可以优化,把数离散化后,对每个长度 $j$ 开一个权值数状数组维护
求出 $f$ 以后 $F[i]=(\sum_{j=1}^{n}f[j][i]) \cdot (n-i)!$(乘上 $(n-i)!$ 是因为那 $n-i$ 个删掉的数可以按任意顺序删除)
最后 $Ans=\sum_{i=1}^{n}F[i]$
具体看代码
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2007,mo=1e9+7; int n,B[N],m,fac[N],Ans; struct dat{ int v,id; inline bool operator < (const dat &tmp) const { return v<tmp.v; } }A[N]; int f[N][N],g[N],ans[N]; int t[N][N]; inline int fk(int x) { return x>=mo ? x-mo : x; } inline void add(int p,int x,int y) { while(x<=m) t[p][x]=fk(t[p][x]+y),x+=x&-x; } inline int query(int p,int x) { int res=0; while(x) res=fk(res+t[p][x]),x-=x&-x; return res; } int main() { n=read(); for(int i=1;i<=n;i++) A[i].v=read(),A[i].id=i; sort(A+1,A+n+1); for(int i=1;i<=n;i++) { if(i==1||A[i].v!=A[i-1].v) m++; B[A[i].id]=m;//离散化 } fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mo; for(int i=1;i<=n;i++) { f[i][1]=1; for(int j=2;j<=n;j++) f[i][j]=query(j-1,B[i]); for(int j=1;j<=n;j++) add(j,B[i],f[i][j]); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) g[j]=fk(g[j]+f[i][j]); for(int i=n;i>=1;i--) { Ans=fk(Ans+ 1ll*g[i]*fac[n-i]%mo ); if(i!=n) Ans=fk(Ans+mo - 1ll*g[i+1]*fac[n-i-1]%mo*(i+1)%mo ); } printf("%d",Ans); return 0; }