JZOJ 5429 排列

排列

Description

有两个长度为 n 的排列A B ,定义排列的价值f( A ,B)为所有满足 Ai > Bi 的位置i的数量。
现给出 n A B S,其中 A B中有一些位置的数未知,问有多少种可能的填数的方案使得 f (A, B )=S
若第 i 位上位置则为0
答案可能很大,对 109 + 7 取模。

Data Constraint

1<= S <=n<= 4103
保证不存在一个位置 i 满足Ai= 0 Bi= 0

Solution

很显然可以把问题拆成两个完全一样的问题。
a序列中缺的数取出来排好序,把 b 序列中失配的数取出来。
ki表示 a 序列中第i个数做多能够匹配到 b 序列中的第ki个数。
fi,j 表示 a 序列中的数做到了第i位,其中有 j 对匹配造成了贡献。
易得fi,j= fi1,j + fi1,j1 *( ki - j +1)
Gi 表示整个 a 序列已处理完,保证有j的贡献的方案数(贡献至少为 j )。
易得Gi= fn,i *( n -i) !
Fi表示贡献恰好为 i 的方案数。
通过容斥原理可得Gi= j>=i Fj * Cij
移项可得 Fi = Gi - j>i Fj * Cij
这样子问题就解决了,最后答案的计算只需合并两个子问题的答案即可。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define fo(i,j,l) for(int i=j;i<=l;i++)
#define fd(i,j,l) for(int i=j;i>=l;i--)

using namespace std;
typedef long long ll;
const ll N=45e2,mo=1e9+7;
ll f1[N],g1[N][N],f2[N],g2[N][N],a[N],b[N],c[N],d[N],e[N],least[N];
int n,m,j,k,l,i,o,p,u,v;
ll jc[N],ny[N],r1[N],r2[N];

ll ksm(ll o,ll t)
{
    ll yy=1;
    for(;t;t>>=1,o=o*o%mo)
    if(t%2)yy=yy*o%mo;
    return yy;
}

int min(int a,int b)
{if(a<b)return a;else return b;}

ll C(ll a,ll b)
{
    return (jc[a]*ny[b]%mo)*ny[a-b]%mo;
}

void work1()
{
    int k=0,v=0;
    fo(i,1,n)if(!r1[i])e[++k]=i;
    fo(i,1,k){
        for(;e[i]>d[v+1]&&v<k;)v++;
        least[i]=v;
    }
    fo(i,0,k)g1[i][0]=1;
    fo(i,1,k)
    fo(l,1,min(least[i],i))
    g1[i][l]=(g1[i-1][l]+g1[i-1][l-1]*(least[i]-l+1))%mo;
    f1[k]=g1[k][k];
    fd(i,k-1,1){
        f1[i]=g1[k][i]*jc[k-i]%mo;
        fo(j,i+1,k)f1[i]=(f1[i]-f1[j]*C(j,i))%mo;
    }
    f1[0]=jc[k];
    fo(i,1,k)f1[0]=(f1[0]-f1[i])%mo;
}

void work2()
{
    int k=0,v=0;
    fd(i,n,1)if(!r2[i])e[++k]=i;
    fo(i,1,k){
        for(;e[i]<c[v+1]&&v<k;)v++;
        least[i]=v;
    }
    fo(i,0,k)g2[i][0]=1;
    fo(i,1,k)
    fo(l,1,min(least[i],i))
    g2[i][l]=(g2[i-1][l]+g2[i-1][l-1]*(least[i]-l+1))%mo;
    f2[k]=g2[k][k];
    fd(i,k-1,1){
        f2[i]=g2[k][i]*jc[k-i]%mo;
        fo(j,i+1,k)f2[i]=(f2[i]-f2[j]*C(j,i))%mo;
    }
    f2[0]=jc[k];
    fo(i,1,k)f2[0]=(f2[0]-f2[i])%mo;
}

int main()
{
    cin>>n>>m;
    jc[0]=ny[0]=1;
    fo(i,1,n)jc[i]=jc[i-1]*i%mo,ny[i]=ksm(jc[i],mo-2);
    fo(i,1,n)scanf("%d",&a[i]);
    fo(i,1,n)scanf("%d",&b[i]);
    fo(i,1,n){
        if(!a[i])d[++u]=b[i]; r1[a[i]]++;
        if(!b[i])c[++v]=a[i]; r2[b[i]]++;
        if((a[i]*b[i]))m-=(a[i]>b[i]);
    }
    sort(d+1,d+u+1);    sort(c+1,c+v+1);
    fo(i,1,v/2)swap(c[i],c[v-i+1]);
    work1(); work2();
    ll ans;
    fo(i,1,m)
    ans=(ans+f1[i]*f2[m-i])%mo;
    ans=(ans+mo)%mo;
    printf("%lld",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值