BZOJ 4361 isn 动态规划+树状数组+容斥原理

Description

给出一个长度为n的序列A(A1,A2…AN)。如果序列A不是非降的,你必须从中删去一个数,
这一操作,直到A非降为止。求有多少种不同的操作方案,答案模10^9+7。
Input

第一行一个整数n。
接下来一行n个整数,描述A。
Output

一行一个整数,描述答案。
Sample Input

4
1 7 5 3
Sample Output

18
HINT

1<=N<=2000

传送门
不会做。。orz……

直接推总的方案推一推,可以发现(似乎)是不可优化成O(N^2)的了。。
考虑容斥。
dp1[i][j]表示以i结尾,长度为j的不下降子序列数量,
f[i]表示长度为i的不下降子序列数量。
根据题意,删除到序列为不下降的时候,就应该停止。
也就是说对于长度(x+1),再删除,就是不合法的。
枚举这个x,我们就可以只考虑(x+1)删除一个变成了长度为x,
那么这个不合法的方案数目就是
f[x+1](x+1)P(n(x+1))
意义就是对于任意一个长度为(x+1)的子序列,可以删掉任意一个位置,
那么每种子序列都有(x+1)种选择;
而题目要求的是操作序列,每种(x+1)的子序列一定删掉了(n-(x+1))个数,
那么这些数删除的顺序任意,也就是它们的全排列.
也就是 f[x+1](x+1)(nx1)!
其中x=0~(n-1)
总的方案数也一样,枚举剩余的子序列长度x,再利用全排列:
f[x](nx)!
其中x=1~n。。

还剩下一个问题就是dp1[i][j]怎么处理出来。
dp1[i][j]=dp1[k][j1],k<i,a[k]<=a[i]
显然是O(N^3)的,不过利用树状数组就可以搞到O(N^2*logN)了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int 
    N=2005,
    mod=1000000007;
int n,a[N],fac[N],tr[N][N];
int g[N],f[N][N];
struct TMP{int x,id;}tmp[N];
bool cmp(TMP a,TMP b){return a.x<b.x;} 
void add(int x,int y,int val){
    while (x<=n) tr[y][x]=(tr[y][x]+val)%mod,x+=x&-x;
}
int query(int x,int y){
    int val=0;
    while (x) val=(val+tr[y][x])%mod,x-=x&-x;
    return val;
}
void Pre(){
    f[1][1]=1,add(a[1],1,1);
    for (int i=2;i<=n;i++){
        for (int j=2;j<=i;j++) f[i][j]=query(a[i],j-1);
        f[i][1]=1,add(a[i],1,1);
        for (int j=2;j<=i;j++) add(a[i],j,f[i][j]);
    }
    for (int i=1;i<=n;i++){
        for (int j=1;j<=i;j++) g[j]=(g[j]+f[i][j])%mod;
    }
    fac[0]=1;
    for (int i=1;i<=n;i++)
        fac[i]=(ll)fac[i-1]*i%mod;
}
int count(){
    int ans=0;
    for (int i=1;i<=n;i++) ans=(ans+(ll)g[i]*fac[n-i]%mod)%mod;
    for (int i=2;i<=n;i++)
        ans=(ans-(ll)g[i]*i%mod*fac[n-i]%mod+mod)%mod;
    return ans;
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&tmp[i].x),tmp[i].id=i;
    sort(tmp+1,tmp+1+n,cmp);
    int rk=1;a[tmp[1].id]=1;
    for (int i=2;i<=n;i++){
        rk=rk+(tmp[i].x!=tmp[i-1].x);
        a[tmp[i].id]=rk;
    }
    Pre();
    printf("%d\n",count());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值