NOIP2016 D2T1 组合数问题

洛谷P2822

 

数学真重要啊……

 

其实解这一题的关键就是组合恒等式:C(n,m)=C(n-1,m)+C(n-1,m-1),然后再知道组合数的矩阵(杨辉三角)和题中n,m的关系就很容易解决了(然而做这题之前我并不知道组合恒等式于是杯具了)

由上文提到的几何恒等式,我们可以将组合数打成如下矩阵(C(i,j)行数代表i,列数代表j(均从0开始))

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

………………

 

接下来我们来看要求的结论:求所有的0≤i≤n,0≤j≤min(i,m)中有多少对(i,j)使C(i,j)是k的倍数。

我乍一看到min(i,m)以为这题很复杂。但放到图中一看,想到i≥j,我们惊奇地发现,我们要求的结论就是图中共n+1行,m+1列(包括i=0,j=0)中有多少组合数是k的倍数。

画个图给大家感受一下(灵魂画手)

比如n=3,m=2,那我们就是要求图中这个部分组合数是k的倍数的数量

 

但询问量特别大,所以我们可以预处理一下

每访问到一个元素都枚举一个矩形很慢,但我们有递推式:d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+!(zuhe[i][j]%k)

拿上面那个矩阵举例子

黑色部分的信息(被k整除的组合数个数)d[3][2]可以表示为红色部分的信息d[2][2]加上绿色部分的信息d[3][1]减去蓝色部分的信息d[2][1](此信息满足区间加法和减法性质)

事实上因为c++数组自动初始化为0的问题,所以我们只会初始化有组合数部分的d值(即i≥j),那么m<n的时候怎么办呢?

很显然图中黑色部分的信息等于红色部分的信息(因为组合数数量相等)

所以当m<n时d[m][n]=d[m][m](在递推的时候处理或是在输出的时候处理均可,我是在输出的时候处理的)

另外此处递推的时候注意一下边界问题(i=j时),此时d[i-1][j]我们没有更新(因为i-1<j),所以此时的d[i][j]=d[i][j-1]+!(zuhe[i][j]%k)

即黑色部分的信息等于红色部分的信息加上当前点的信息

 

还有一点小tip就是2000个组合数会很大,所以我们递推组合数的时候就将它模k,这样模k=0的组合数在表中的值就是0,上面的递推公式就改为:d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+!zuhe[i][j](i>j),d[i][i]=d[i][i-1]+!zuhe[i][i]

 

剩下的就很简单了,记得初始化zuhe[1][0]=zuhe[1][1]=1即可

代码如下:

 1 #include<cstdio>
 2 using namespace std;
 3 int t,k,n,m;
 4 int zuhe[2001][2001];//存储组合数(%k意义下) 
 5 int d[2001][2001];//存储方案个数 
 6 int main()
 7 {
 8     scanf("%d%d",&t,&k);
 9     zuhe[1][0]=zuhe[1][1]=1;//初始化C(1,0)=C(1,1)=1 
10     for(int i=2;i<=2000;i++)
11         for(int j=0;j<=i;j++)
12             zuhe[i][j]=(zuhe[i-1][j]+zuhe[i-1][j-1])%k;
13     for(int i=1;i<=2000;i++)
14     {
15         for(int j=1;j<i;j++)
16             d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+!zuhe[i][j];
17         d[i][i]=d[i][i-1]+!zuhe[i][i];
18     }
19     while(t--)
20     {
21         int n,m;
22         scanf("%d%d",&n,&m);
23         if(n<m)
24             m=n;
25         printf("%d\n",d[n][m]);
26     }
27     return 0;
28 }

 

posted @ 2018-08-20 11:03  李昊哲  阅读(288)  评论(0编辑  收藏  举报