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

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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章