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;
}

 

posted @ 2019-04-28 17:07  LLTYYC  阅读(181)  评论(0编辑  收藏  举报