【BZOJ3622】已经没有什么好害怕的了
Description
Input
Output
Sample Input
5 35 15 45
40 20 10 30
Sample Output
HINT
输入的2*n个数字保证全不相同。
题解:好吧这题不是神题,而是套路题,容斥+DP的套路在很多题中都用到过,不过我虽然知道套路,却被这题的第一步卡住了。
我们将两个序列从小到大排序。
好吧这步看起来可能很水,正常人看到无序的序列都会先想到排序,然而为什么要两个都从小到大排呢?排序的意义何在?一会讲。
题中给的是(糖果>药片)=(药片>糖果)+k,我们只需要用一点小学数学的知识就能算出(糖果>药片)的具体数量。下面是套路部分:我们设f[i][j]表示枚举到前i个,(糖果>药片)数至少为j的方案数。然后我们预处理出最大的k,满足药片k<糖果i,那么方程就是:
$f[i][j]=f[i-1][j]+f[i-1][j-1]*(k-j+1)$
现在知道排序的意义了吧?因为后面的i的决策区间一定包含前面的i的决策区间,所以只需要将其减去即可防止重复。
那么最后答案是什么呢?套路:ans=至少有k个的方案数-至少有k+1个的+至少有k+2个的方案数-。。。
所以答案=$sumlimits_{j=k}^nf[n][j](-1)^{j-k}C_j^k$
P.S:这里解释一下容斥系数是$C_j^k$的原理吧,首先我们用f[i]代表上面的f[n][i],用g[i]代表恰好有i个符合条件的方案数。根据定义有:$f[i]=sumlimits_{j=i}^ng[i]C_j^i$。所以我们上面得到的最终式子可以转化成:$sumlimits_{j=k}^nf[j](-1)^{j-k}C_j^k=sumlimits_{i=k}^ng[i]sumlimits_{j=k}^i(-1)^{j-k}C_i^jC_j^k\=sumlimits_{i=k}^ng[i]sumlimits_{j=k}^i(-1)^{j-k}C_i^kC_{i-k}^{j-k}\=sumlimits_{i=k}^ng[i]C_i^ksumlimits_{j=k}^i(-1)^{j-k}C_{i-k}^{j-k}$
根据组合恒等式后面那个东西只在i=k的时候=1,其余时候=0,所以我们就得到了g[k]。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const ll P=1000000009; int n,m; ll ans; ll f[2010][2010],c[2010][2010],jc[2010]; int a[2010],b[2010]; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-') f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+(gc^'0'),gc=getchar(); return ret*f; } int main() { n=rd(),m=rd(); if((n^m)&1) { printf("0"); return 0; } m=(n+m)>>1; int i,j,k; for(jc[0]=i=1;i<=n;i++) jc[i]=jc[i-1]*i%P; for(i=1;i<=n;i++) a[i]=rd(); for(i=1;i<=n;i++) b[i]=rd(); for(i=0;i<=n;i++) { c[i][0]=1; for(j=1;j<=i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%P; } sort(a+1,a+n+1),sort(b+1,b+n+1); f[0][0]=1; for(i=1;i<=n;i++) { for(k=1;k<=n&&b[k]<a[i];k++); k--; for(j=1;j<=i;j++) f[i][j]=(f[i-1][j]+f[i-1][j-1]*max(k-j+1,0))%P; f[i][0]=f[i-1][0]; } ll tmp=1; for(i=m;i<=n;i++) f[n][i]=(f[n][i]*jc[n-i])%P,ans=(ans+tmp*f[n][i]*c[i][m]%P+P)%P,tmp=-tmp; printf("%lld",(ans+P)%P); return 0; }