JZOJ5429. 【NOIP2017提高A组集训10.27】排列 DP+容斥

92 篇文章 1 订阅
73 篇文章 0 订阅

Description

有两个长度为n的排列A和B,定义排列的价值f(A,B)为所有满足A[i]>B[i]的位置i的数量。
现给出n,A,B和S,其中A和B中有一些位置的数未知,问有多少种可能的填数的方案使得f(A,B)=S

Input

第一行两个整数n和S
第二行n个数表示排列A
第三行n个数表示排列B
其中A和B中某些位置是0表示当前位置上的数还未确定,保证不存在一个位置i满足A[i]=0且B[i]=0

Output

一行一个整数表示使得f(A,B)=S的填数的方案数,由于答案可能很大,对10^9+7取模。

Sample Input

4 2
4 2 0 0
0 0 4 2

Sample Output

2

Data Constraint

对于20%的数据满足,1<=n<=10
对于50%的数据满足,1<=n<=20
对于70%的数据满足,1<=n<=200
对于100%的数据满足,1<=S<=n<=4000
保证不存在一个位置i满足A[i]=0且B[i]=0

比赛的时候看错题了,以为是个水题。。发现以后只会做50分,弱爆了。。
这题与bzoj3198有异曲同工之妙,都是组合数作为容斥系数。
首先可以发现,上或者下为0是相同的处理方法,所以这里只讨论a[i]=0,b[i]!=0的情况。
把这些列抽出来,存在u,v里面,然后进行DP。
可以发现,此时并不需要遵守同下标的要求,任意两个之间只要大于号成立皆可配对,于是我们使用组合数处理,但是由于会算重,所以进行容斥。
具体来说就是
dp[i][j]+=dp[i+1][j1](totx+1j+1)
f[i]+=c(dp[1][j]C(j,i)fac[totj]) c是容斥系数。
分成两个部分处理即可。
那么最终答案就是 g[mi]f[i]
%%%ymw。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=4e3+5;
int n,m,a[N],b[N],ans;
int f[N],g[N],a1[N],b1[N];
int dp[N][N],vis[N];
const int mo=1e9+7;
typedef long long ll;
ll fac[N],inv[N];
inline ll pow(ll a,ll b)
{
    ll ret=1;
    while (b)
    {
        if (b&1)ret=ret*a%mo;
        a=a*a%mo;
        b>>=1;
    }
    return ret;
}
inline int C(int n,int m)
{
    return 1ll*fac[n]*inv[m]%mo*inv[n-m]%mo;
}
inline void solve1()
{
    memset(vis,0,sizeof(vis));
    int tot=0;
    fo(i,1,n)vis[a[i]]=1;
    fo(i,1,n)if (!vis[i])a1[++tot]=i;
    tot=0;
    fo(i,1,n)if (!a[i])b1[++tot]=b[i];
    sort(b1+1,b1+tot+1);
    fo(i,1,tot+1)dp[i][0]=1;
    fo(j,1,tot)
    {
        int x=tot+1;
        fd(i,tot,1)
        {
            dp[i][j]=dp[i+1][j];
            while (x>1&&b1[i]<a1[x-1])x--;
            if (tot-x+1>j-1)
            dp[i][j]=
            (dp[i][j]+1ll*dp[i+1][j-1]*(tot-x+1-j+1)%mo)%mo; 
        }
    }
    fo(i,0,tot)
    {
        fo(j,i,tot)
        if ((j-i)%2==0)
        {
            f[i]=(f[i]+1ll*dp[1][j]*C(j,i)%mo*fac[tot-j]%mo)%mo;
        }
        else 
        {
            f[i]=(f[i]-1ll*dp[1][j]*C(j,i)%mo*fac[tot-j]%mo)%mo;
            if(f[i]<0)f[i]+=mo;
        }
    }
}
inline void solve2()
{
    memset(vis,0,sizeof(vis));
    int tot=0;
    fo(i,1,n)vis[b[i]]=1;
    fo(i,1,n)if (!vis[i])b1[++tot]=i;
    tot=0;
    fo(i,1,n)if (!b[i])a1[++tot]=a[i];
    sort(a1+1,a1+tot+1);
    memset(dp,0,sizeof(dp));
    fo(i,1,tot+1)dp[i][0]=1;
    fo(j,1,tot)
    {
        int x=tot+1;
        fd(i,tot,1)
        {
            dp[i][j]=dp[i+1][j];
            while (x>1&&b1[i]<a1[x-1])x--;
            if (tot-x+1>j-1)
                dp[i][j]=(dp[i][j]+1ll*dp[i+1][j-1]*(tot-x+1-j+1)%mo)%mo; 
        }
    }
    fo(i,0,tot)
    {
        fo(j,i,tot)
        if ((j-i)%2==0)
        {
            g[i]=(g[i]+1ll*dp[1][j]*C(j,i)%mo*fac[tot-j]%mo)%mo;
        }
        else 
        {
            g[i]=(g[i]-1ll*dp[1][j]*C(j,i)%mo*fac[tot-j]%mo)%mo;
            if(f[i]<0)f[i]+=mo;
        }
    }
}
int main()
{
    freopen("arrange.in","r",stdin);
    freopen("arrange.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n)scanf("%d",&a[i]);
    fo(i,1,n)scanf("%d",&b[i]);
    fac[0]=inv[0]=1;
    fo(i,1,n)fac[i]=1ll*fac[i-1]*i%mo,inv[i]=pow(fac[i],mo-2);
    solve1();
    solve2();
    fo(i,1,n)if (a[i]&&b[i]&&a[i]>b[i])m--;
    ll ans=0;
    fo(i,0,m)ans=(ans+1ll*f[i]*g[m-i]%mo)%mo;
    printf("%lld\n",ans%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值