lcez校内模拟赛: 小R与苹果派——题解
首先对两个数组排序。
然后预处理出数组p[i]表示b[x]<a[i]的最大的x。
然后我们设f[i][k]表示对于前i个派,我单独选出来k组a[y]>b[y]。(即此时有k组a>b的匹配,其余还未匹配)
显然f[i][k]=f[i-1][k]+f[i-1][k-1]*(p[i]-(k-1))。等号右边的第一项相当于考虑a[i]不分配b,第二项相当于a[i]分配b。
这里还要注意一下f[0][0]=f[1][0]=f[2][0]=...=ff[n][0]=1的边界条件。
但是这个数组肯定不是答案。因为这里f[i][k]中保证了只考虑到A的前i个,B的所有位置,并且满足只给A>B的k个A分配了B, 其余A和B没有配对。
我们可以再设g[i]表示对于前n个派,恰好有i组a[x]>b[x]的方案数。
借助容斥原理思考一番后,可得转移方程:
g[i]=f[n][i]*(n-i)!- g[j]*c(j,i) (i+1<=j<=n,c是组合数)。
这里等号右边的第一项相当于只分配了B的i个A的方案数*没分配B的(n-i)个A分配B的方案数(阶乘项)。这是是所有a>b的匹配对数>=i对的方案数,但注意这里可能会出现同一种分配多次出现的情况(比如3个位置,1、2分配了1、2 , 3对应3;1、3分配了1、3 , 2对应2),所以要减掉的g[j]还要乘上个组合数来减掉重复出现的方案数。
考虑最终答案是什么。“A 做的苹果派比 B 做的苹果派美味的天数比 B 做的比 A 做的美味的天数恰好多 k。”设A 做的苹果派比 B 做的苹果派美味的天数为x, B 做的比 A 做的美味的天数为y。则有方程组:
x+y=n;
x-y=k;
解得x=(n+k)/2
由此知道的答案即为g[(n+k)/2],同时知道当(n+k)为奇数时是无解的。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 5 using namespace std; 6 7 const int N=2005; 8 9 typedef long long LL; 10 11 const LL mod=1e9+9; 12 13 int n,k,a[N],b[N],p[N],s; 14 15 LL jc[N],f[N][N],g[N],c[N][N]; 16 17 char ch; 18 19 inline int read() 20 { 21 int x=0; 22 ch=getchar(); 23 while(!isdigit(ch)) 24 ch=getchar(); 25 while(isdigit(ch)) 26 x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 27 return x; 28 } 29 30 inline void init() 31 { 32 jc[0]=jc[1]=1; 33 for(int i=2;i<=n;++i) 34 jc[i]=jc[i-1]*i%mod; 35 c[0][0]=1; 36 for(int i=1;i<=n;++i) 37 { 38 c[i][0]=1; 39 for(int j=1;j<=i;++j) 40 c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; 41 } 42 } 43 44 int main() 45 { 46 n=read(),k=read(); 47 for(int i=1;i<=n;++i) 48 a[i]=read(); 49 for(int i=1;i<=n;++i) 50 b[i]=read(); 51 if((n+k)&1) 52 { 53 cout<<0; 54 return 0; 55 } 56 s=(n+k)>>1; 57 sort(a+1,a+1+n); 58 sort(b+1,b+1+n); 59 int las=0; 60 for(int i=1;i<=n;++i) 61 { 62 while(b[las+1]<a[i]&&las+1<=n) 63 las++; 64 p[i]=las; 65 } 66 init(); 67 f[0][0]=1; 68 for(int i=1;i<=n;++i) 69 { 70 f[i][0]=1; 71 for(int j=1;j<=i;++j) 72 f[i][j]=(f[i-1][j]+f[i-1][j-1]*(p[i]-j+1))%mod; 73 } 74 g[n]=f[n][n]; 75 for(int i=n-1;i>=s;--i) 76 { 77 g[i]=f[n][i]*jc[n-i]%mod; 78 for(int j=i+1;j<=n;++j) 79 g[i]=(g[i]-g[j]*c[j][i])%mod; 80 if(g[i]<0) 81 g[i]+=mod; 82 } 83 printf("%lld",g[s]); 84 return 0; 85 }
这个DP在考场上几乎没有人写出来。为什么这么难?我再次略总结一下:
1、这个DP出现了不只一个转移方程,即一个转移方程解决不了这个问题, 必须要分步处理,每一步都是个DP。我们做这个题,要考虑怎么分步、步骤之间的联系、每步的处理方式。而这就很难想了。
2、用到了容斥原理的数学知识,对数学基础不行的同学(尤其是作者)极为不友好。