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 }
AC代码

 

这个DP在考场上几乎没有人写出来。为什么这么难?我再次略总结一下:

1、这个DP出现了不只一个转移方程,即一个转移方程解决不了这个问题, 必须要分步处理,每一步都是个DP。我们做这个题,要考虑怎么分步、步骤之间的联系、每步的处理方式。而这就很难想了。

2、用到了容斥原理的数学知识,对数学基础不行的同学(尤其是作者)极为不友好。

 

posted @ 2019-11-10 15:42  千叶繁华  阅读(297)  评论(0编辑  收藏  举报