1. 程式人生 > >Luogu P2822 組合數問題(字首和)

Luogu P2822 組合數問題(字首和)

P2822 組合數問題

題意

題目描述

組合數\(C_n^m\)表示的是從\(n\)個物品中選出\(m\)個物品的方案數。舉個例子,從\((1,2,3)\)三個物品中選擇兩個物品可以有\((1,2),(1,3),(2,3)\)這三種選擇方法。根據組合數的定義,我們可以給出計算組合數\(C_n^m\)的一般公式:

\[C_n^m=\frac{n!}{m!(n-m)!}\]

其中\(n!=1\times 2\times \cdots \times n\);特別地,定義\(0!=1\)

小蔥想知道如果給定\(n,m\)\(k\),對於所有的\(0\leq i\leq n,0\leq j\leq \min \left( i, m \right)\)

有多少對\((i,j)\)滿足\(C_i^j\)\(k\)的倍數。

輸入輸出格式

輸入格式:

第一行有兩個整數\(t,k\),其中\(t\)代表該測試點總共有多少組測試資料,\(k\)的意義見問題描述。

接下來\(t\)行每行兩個整數\(n,m\),其中\(n,m\)的意義見問題描述。

輸出格式:

\(t\)行,每行一個整數代表所有的\(0\leq i\leq n,0\leq j\leq \min \left( i,m\right)\)中有多少對\((i,j)\)滿足\(C_i^j\)\(k\)的倍數。

輸入輸出樣例

輸入樣例#1:

1 2
3 3

輸出樣例#1:

1

輸入樣例#2:

2 5
4 5
6 7

輸出樣例#2:

0
7

說明

【樣例1說明】

在所有可能的情況中,只有\(C_2^1=2\)\(2\)的倍數。

【子任務】

P2822

思路

\(10\)個月以前,當我和一位數競黨聊起這道題的時候,他啟發我,可以利用\(k\)的特性來特判每一個數據點。當時的我嫌麻煩,沒有這樣寫。如今問了\(Mercury\)這道題的做法,才發現正解才是\(OI\)思維,之前的想法太偏數學了。

首先,楊輝三角的值與組合數相同,我們可以用求楊輝三角的方法很快求出組合數。在求的過程中,組合數對\(k\)取模,若該位為\(0\),則說明它是\(k\)的倍數。

然後就是這道題的精髓了:用一個二維陣列\(s[i][j]\)

統計組合數為\(0\)的情況的字首和。轉移方法是:\(s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+[c[i][j]=0]\)

然後直接輸出字首和就好啦。

AC程式碼

#include<bits/stdc++.h>
using namespace std;
int t,k,a[2005][2005],s[2005][2005];
int read()
{
    int re=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) re=(re<<3)+(re<<1)+ch-'0',ch=getchar();
    return re;
}
int main()
{
    t=read(),k=read();
    a[1][1]=1;
    for(int i=2;i<=2001;i++)
    {
        for(int j=1;j<=i;j++) a[i][j]=(a[i-1][j-1]+a[i-1][j])%k;
        for(int j=1;j<=i;j++) s[i][j]=s[i][j-1]+(!a[i][j]);
        for(int j=i+1;j<=2001;j++) s[i][j]=s[i][i];
        for(int j=1;j<=2001;j++) s[i][j]+=s[i-1][j];
    }
    while(t--)
    {
        int x=read(),y=read();
        printf("%d\n",s[x+1][y+1]);
    }
    return 0;
}