P2822 组合数问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这道题本身倒是不难,但是卡时间卡的太严重了。对于t是10^4次方查找,我们递归将C(n,m)求出来,并用一个数组记忆化保存,然后对于每次查询,我们循环遍历就可以了~

#include <iostream>
#include <cstring>
using namespace std;
#define Max 2005
typedef long long int ll;
ll C[Max][Max];
ll get_c(int n,int m);
    int main()
    {
        int res=0;
        memset(C,0, sizeof(C));
        int t,k,n,m;
        cin>>t>>k;
        for(int e=1;e<=t;e++)
        {
            res=0;
            cin>>n>>m;
            for(int i=0;i<=n;i++)
            {
                for(int j=0;j<=min(m,i);j++)
                {
                    if(!(get_c(i,j)%k))
                    {
                        res++;
                    }
                }
            }
            cout<<res<<endl;
        }
        return 0;
    }

    ll get_c(int n,int m)
    {
        if(C[n][m])
        {
           return C[n][m];
        }
        else if(n==m||m==0)
        {
            return C[n][m]=1;
        }
        else
        {
            return C[n][m]=get_c(n-1,m-1)+get_c(n-1,m);
        }
    }

蜜汁自信,果然。后面有几个数据溢出WA掉了,有几个数据超时了。

在这里插入图片描述
对于WA掉的数据,很好处理,只需要先办法使得他不溢出就行了呗~

防止溢出我们可以用取余的办法,这个办法可以防止溢出并且使得最后的结果不会变~

在这里插入图片描述

如图所示,我们将所有的Cn,m (n,m<=2000) 全部算出来

		C[0][0]=1;
        C[1][0]=C[1][1]=1;
        for(int i=2;i<=2000;i++)
        {
            C[i][0]=1;
            for(int j=1;j<=i;j++)
            {
                C[i][j]=C[i-1][j-1]+C[i-1][j];
            } 
        }

C[i][j]即使是long long还是避免不了溢出,所以我们取余想办法,取余可以但是不能瞎取余

首先呢,对于取余运算有一个公式:(a+b)%r=(a%r+b%r)%r

在这里插入图片描述我们设C10 =a C11=b, C21就等于a+b C20=c C22=d

C31 =a+b+c C32=a+b+d

我们可以在计算完Ci,j以后将其直接%k,如果结果是0,那么就用一个数组flag[i][j]=1,来标记,表示Ci,j可以整除k。

但是要如何取模,并证明正确性呢?看到上面那个图:

C31%k=(a+b+c)%k;

C21%k=(a+b)%k;

C20%k=c%k

(c%k+(a+b)%k)%k==(a+b+c)%k

左边等于右边,所以这样取模结果成立,代码如下~

 for(int i=2;i<=2000;i++)
        {
            C[i][0]=1;
            for(int j=1;j<=i;j++)
            {
                C[i][j]=(C[i-1][j-1]+C[i-1][j])%k;
                if(!C[i][j])
                {
                    flag[i][j]=1;
                }
            }
        }

这样就可以避免溢出了~

虽然这样可以避免溢出但是避免不了超时,这时候就要用到浅醉和了~

如果我么输入 n=3 m=3
在这里插入图片描述
就是这样~
我们要找的就是
在这里插入图片描述
按照我们上面那个算法取模计算的话,我们是统计这些变量有多少个值为0,就是要输出的结果了~
然鹅,t<=10^4 这么多操作,如果每次都去统计恐怕力不从心了。结果可想而知,肯定是会T掉2个点,这个题的测试点有20个。。。。。

所以这时候要轮到我们前缀和登场了~

二维数组前缀和直接用这样一个递归公式

a[i][j]=a[i][j]+a[i-1][j]+a[i][j-1]-a[i-1][j-1]

a[i][j]表示左上角是(0,0),右下角是(i,j)的正方形or长方形区域里面的值的和

如图:
在这里插入图片描述
a[2][2]表示图中正方形里面值的和

上面那个前缀和递归公式不太理解的同学建议取看看其他dalao的博客~
比如这个dalao的博客

我们在计算a[2]2[] 的时候,就是a[2][2]=a[2][2]+a[2][1]+a[1][2]-a[1][1]
因为我们在遍历这个杨辉三角的时候压根不会遇到a[1][2] ,所以此时a[1][2]=0,所以我们依次计算出。。。a[1][0] a[1][1] 以后还要将a[1][2]=a[1][1]

也就是说我们每次内层循环结束以后一定要搞个复制操作将a[i][i+1]=a[i][i]

下面是ACcode~

#include <iostream>
#include <cstring>
using namespace std;
#define Max 2005
int a[Max][Max];
int  C[Max][Max];
int t,k,n,m;
    int main()
    {

        C[0][0]=1;
        C[1][0]=C[1][1]=1;
        cin>>t>>k;
        memset(a,0, sizeof(a));
        if(k==1)
        {
            a[0][0]=1;
            a[1][0]=2;
            a[1][1]=3;
            a[1][2]=3;
        }
        for(int i=2;i<=2000;i++)
        {
            C[i][0]=1;
            if(k==1)
            {
                a[i][0]=a[i-1][0]+1;
            }
            for(int j=1;j<=i;j++)
            {
                C[i][j]=(C[i-1][j-1]+C[i-1][j])%k;
                if(!C[i][j])
                {
                    a[i][j]++;
                }
                a[i][j]=a[i][j]+a[i-1][j]+a[i][j-1]-a[i-1][j-1];
            }
            a[i][i+1]=a[i][i];
        }
        for(int e=1;e<=t;e++)
        {
            cin>>n>>m;
            if(m>n)
            {
                m=n;
            }
            cout<<a[n][m]<<endl;
        }
        return 0;
    }


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值