bzoj3622-已经没有什么好害怕的的了

题意

给出两个长度为 \(n\) 的数列 \(a,b\)\(2n\) 个数都互不相同,求有多少种对应方式使得 \(a_i>b_i\) 的个数比 \(a_i<b_i\) 的个数恰好多 \(k\)\(n\le 2000\)

分析

容易把问题转化成有多少种对应方案使得 \(a_i>b_i\) 的个数恰好多 \(m\) 。这是一个序列上的计数问题,一种经典的思路是分阶段考虑。

首先给 \(a\) 排序,预处理出 \(b\) 中有多少个数比 \(a_i\) 小,记为cnt[i] 。分阶段考虑,设 f[i][j] 表示给 \(a\) 的前 \(i\) 个分配 \(j\) 个小于它们的的方案数。那么有转移:
\[ f[i][j]=f[i-1][j]+f[i-1][j-1]*(cnt[i]-(j-1)) \]
除了这 \(j\) 个以外其他是随便选的。这就导致了计算重复,所以我们考虑如何减去重复。设 \(g_i\) 表示整个对应中恰好有 \(i\) 个小于它们的,那么有:
\[ g[i]=f[n][i](n-i)!-重复 \]
这里最妙的方法是,重复我们反过来求。重复的是什么呢?就是随便匹配的过程中得到的那些 \(a_i>b_i\) 的方案。这些方案在 \(g[k],k>i\) 中是包含的!!\(g_k\) 中每一个方案被 \(f[n][i]\) 算多了 \(\binom k i\) 次.
\[ g[i]=f[n][i](n-i)!-\sum _{i<k\le n}\binom k ig[k] \]

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long giant;
inline char nchar() {
    static const int bufl=1<<20;
    static char buf[bufl],*a,*b;
    return a==b && (b=(a=buf)+fread(buf,1,bufl,stdin),a==b)?EOF:*a++;
}
inline int read() {
    int x=0,f=1;
    char c=nchar();
    for (;!isdigit(c);c=nchar()) if (c=='-') f=-1;
    for (;isdigit(c);c=nchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=2e3+1;
const int q=1e9+9;
inline int Plus(int x,int y) {return ((giant)x+(giant)y)%q;}
inline int Sub(int x,int y) {return Plus(x,q-y);}
inline int Multi(int x,int y) {return (giant)x*y%q;}
inline int mi(int x,int y) {
    int ret=1;
    for (;y;y>>=1,x=Multi(x,x)) if (y&1) ret=Multi(ret,x);
    return ret;
}
inline int inv(int x) {return mi(x,q-2);}
int a[maxn],b[maxn],f[maxn],g[maxn],le[maxn],fac[maxn],ifac[maxn];
inline int C(int n,int m) {return Multi(Multi(fac[n],ifac[m]),ifac[n-m]);}
int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in","r",stdin);
#endif
    int n=read();
    int k=read();
    if ((n+k)&1) puts("0"),exit(0);
    fac[0]=ifac[0]=fac[1]=ifac[1]=1;;
    for (int i=2;i<=n;++i) ifac[i]=inv(fac[i]=Multi(fac[i-1],i));
    for (int i=1;i<=n;++i) a[i]=read();
    for (int i=1;i<=n;++i) b[i]=read();
    sort(a+1,a+n+1),sort(b+1,b+n+1);
    for (int i=1,j=0;i<=n;++i) {
        for (;j<n && b[j+1]<a[i];++j);
        le[i]=j;
    }
    f[0]=1;
    for (int i=1;i<=n;++i) for (int j=i;j;--j) f[j]=Plus(f[j],Multi(f[j-1],max(le[i]-j+1,0)));
    g[n]=f[n];
    for (int i=n-1;i;--i) {
        int &gi=g[i]=0;
        for (int j=i+1;j<=n;++j) gi=Plus(gi,Multi(g[j],C(j,i)));
        gi=Sub(Multi(f[i],fac[n-i]),gi);
    }
    printf("%d\n",g[(n+k)>>1]);
    return 0;
}

转载于:https://www.cnblogs.com/owenyu/p/7375281.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值