[JZOJ5429]【NOIP2017提高A组集训10.27】排列

Description

有两个长度为n的排列A和B,定义排列的价值f(A,B)为所有满足A[i]>B[i]的位置i的数量。
现给出n,A,B和S,其中A和B中有一些位置的数未知,问有多少种可能的填数的方案使得f(A,B)=S
对于100%的数据满足,1<=S<=n<=4000
保证不存在一个位置i满足A[i]=0且B[i]=0

Solution

直接做很难做,不妨考虑转化一下
显然上面为0和下面为0的情况是相似的,可以分开考虑,两个都不为0的直接判掉。

下面只讨论上面为0的情况
我们可以把剩下的数拉出来排个序,按顺序选。

F[i][j] 表示选定了前i个,造成了j的贡献。

r 为第i个比对应的b的多少个大。
转移似乎很显然,F[i][j]=F[i1][j]+F[i][j1](r(j1))

最后 F[m][j]=F[m][j](mj)!
把这些没造成贡献的排列一下。

但是,这样是错的。
有可能对于某个点没有造成贡献的,选了后面的b,令后面的即使不得不选前面的而造成了额外的贡献。

所以我们改一下状态
F[i][j] 表示选定了前i个,至少造成了j的贡献。

这样上面的转移成立。

现在要求刚好为j的答案,可以容斥一下。
可以发现 F[m][k],k>j F[m][j] 中多算了 Cjk 次。

容斥一下即可。

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <cstdlib>
#include <set>
#include <map>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define LL long long
#define mo 1000000007
#define N 4005
#define LL long long
using namespace std;
int n,m,a[N],b[N],d[N],s[N];
LL f[N][N],g[N],h[N],js[N],ny[N];
bool bz[N];
LL ksm(LL k,LL n)
{
    LL s=1;
    for(;n;n>>=1,(k*=k)%=mo) if(n&1) (s*=k)%=mo;
    return s;
}
LL zh(LL m,LL n)
{
    return js[n]*ny[m]%mo*ny[n-m]%mo;
}
int main()
{
    cin>>n>>m;
    fo(i,1,n) scanf("%d",&a[i]);
    fo(i,1,n) scanf("%d",&b[i]);
    js[0]=ny[0]=1;
    fo(i,1,n) js[i]=js[i-1]*(LL)i%mo,ny[i]=ksm(js[i],mo-2);
    fo(i,1,n)
    {
        if(a[i]>b[i]&&b[i]!=0) m--;
        if(a[i]==0) s[b[i]]++;
        if(a[i]!=0) bz[a[i]]=1;
    }
    fo(i,1,n) 
    {
        if(!bz[i]) d[++d[0]]=i;
        s[i]+=s[i-1];
    }
    f[0][0]=1;
    fo(i,1,d[0])
    {
        fo(j,0,s[d[i]-1])
        {
            f[i][j]=f[i-1][j];
            if(j) (f[i][j]+=f[i-1][j-1]*(LL)(s[d[i]-1]-j+1)%mo)%=mo;
            if(i==d[0]) (f[i][j]*=js[d[0]-j])%=mo;
        }
    }
    fo(j,0,s[d[d[0]]-1])
    {
        g[j]=f[d[0]][j];
        LL v=-1;
        fo(k,j+1,s[d[d[0]]-1])
        {
            (g[j]+=v*zh(j,k)*f[d[0]][k]%mo)%=mo;
            v=-v;
        }   
    }
    d[0]=0;
    memset(s,0,sizeof(s));
    memset(f,0,sizeof(f));
    memset(bz,0,sizeof(bz));
    fo(i,1,n)
    {
        if(b[i]==0) s[a[i]]++;
        else bz[b[i]]=1;
    }
    fod(i,n,1)  
    {
        if(!bz[i]) d[++d[0]]=i;
        s[i]+=s[i+1];
    }
    f[0][0]=1;
    fo(i,1,d[0])
    {
        fo(j,0,s[d[i]+1])
        {
            f[i][j]=f[i-1][j];
            if(j) (f[i][j]+=f[i-1][j-1]*(s[d[i]+1]-j+1)%mo)%=mo;
            if(i==d[0]) (f[i][j]*=js[d[0]-j])%=mo;
        }
    }
    fo(j,0,s[d[d[0]]+1])
    {
        h[j]=f[d[0]][j];
        LL v=-1;
        fo(k,j+1,s[d[d[0]]+1])
        {
            (h[j]+=v*zh(j,k)*f[d[0]][k]%mo)%=mo;
            v=-v;
        }   
    }
    LL ans=0;
    fo(j,0,m) (ans+=g[j]*h[m-j]%mo)%=mo;
    printf("%lld\n",(ans+mo)%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值