【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

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

Solution

显然A[i]=0的项和b[i]=0的项可以分开处理,a[i],b[i]都不为0的显然可以去掉。问题转化成在一个空的序列上放一些数,使它A[I]>b[i]的数量为k。这种问题很常见,但我现在才会做。我们设f[i][j]表示前i个A数组中我们选择j个使它>b[i],那么f[i][j]=f[i-1][j]+f[i-1][j-1](d[i]-j),d[i]表示比A的第i个数小的B的个数。由于我们只规定了必须有贡献的j个,剩下的随意分配,所以要f[n][i](n-i)!,但这样算的结果不是恰好贡献为j,而是至少贡献为j的方案,我们下面称为f[j]。我们设g[j]表示贡献恰好为j的方案。我们对f[y]的定义仔细研究后发现一个g[x]在f[y] (x>y)中计算Cyx次(因为你会在x个贡献中任意选取y个作为必须贡献)。我们发现g[n]=f[n]的,g[y]=f[y]nx=y+1Cyxg[x]。最后组合乘一下就好。

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const ll maxn=4e3+5,mo=1e9+7;
ll f[maxn][maxn],a[maxn],b[maxn],c[maxn],d[maxn],bz[maxn],g[maxn],p[maxn],ni[maxn],q[maxn],h[maxn];
ll n,m,i,t,j,k,l,x,y,z,ans;
ll mi(ll x,ll y){
    if (y==1) return x;
    t=mi(x,y/2);
    if (y%2) return t*t%mo*x%mo;return t*t%mo;
}
ll pan(ll x,ll y){
    return p[x]*ni[y]%mo*ni[x-y]%mo;
}
void make(int n){
    f[0][0]=1;
    for (i=1;i<=n;i++)
        for (j=0;j<=i;j++)
            f[i][j]=((j<i)*f[i-1][j]+(j!=0 && d[i]>=j)*f[i-1][j-1]*(d[i]-j+1)%mo)%mo;
    for (i=n;i>=0;i--){
        g[i]=f[n][i]*p[n-i]%mo;
        for (j=i+1;j<=n;j++)
            g[i]=(g[i]-pan(j,i)*g[j]%mo+mo)%mo;
    }
}
int main(){
    freopen("arrange.in","r",stdin);freopen("arrange.out","w",stdout);
    scanf("%lld%lld",&n,&m);p[0]=1;
    for (i=1;i<=n;i++)p[i]=p[i-1]*i%mo;
    ni[n]=mi(p[n],mo-2);
    for (i=n;i>=1;i--) ni[i-1]=ni[i]*i%mo;
    for (i=1;i<=n;i++)scanf("%lld",&a[i]),bz[a[i]]++;
    for (i=1;i<=n;i++)scanf("%lld",&b[i]),m-=(a[i]>b[i] && b[i]);
    for (i=1;i<=n;i++)if (!a[i]) c[++c[0]]=b[i];j=0;
    sort(c+1,c+c[0]+1);
    for (i=1;i<=n;i++)if (!bz[i]){
        while (i>c[j+1] && j<c[0]) j++;
        d[++d[0]]=j;
    }
    make(d[0]);swap(q,g);
    c[0]=d[0]=0;memset(bz,0,sizeof(bz));
    for (i=1;i<=n;i++){
        bz[b[i]]++;
        if (!b[i]) h[++h[0]]=a[i];
    }
    for (i=1;i<=n;i++) if (!bz[i]) c[++c[0]]=i;j=0;
    sort(h+1,h+h[0]+1);
    for (i=1;i<=h[0];i++){
        while (h[i]>c[j+1] && j<c[0]) j++;
        d[i]=j;
    }d[0]=h[0];
    make(d[0]);
    for (i=0;i<=m;i++)
        ans=(ans+q[i]*g[m-i]%mo)%mo;
    printf("%lld\n",ans);
}
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/crybymyself/article/details/78388392
文章标签: JZOJ5429 排列 dp
个人分类: GDOI dp 机智题
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭