UVa1635 - Irrelevant Elements(二项式定理)

71 篇文章 0 订阅

题目链接

简介:
对于给定的n个数a1,a2,a3,… ,an,依次求出相邻两个数的和,得到一个新数列
重复上述操作,直到最后变成一个数
问这个数除以m的余数与那些数无关

分析:
我们不妨先来看一个简单的例子:
这里写图片描述
当n=5的时候,“1 4 6 4 1”正好是杨辉三角的第4行

ai的系数就是C(n-1,i-1)

这样,问题就变成了C(n-1,i)中那些是m的倍数

我们可以通过式子:
C(n,k)=C(n,k-1)*(n-k+1)/k
在O(n)的时间内计算出所有系数
这样就解决了吗?
很可惜,因为n的范围太大,我们需要高精度才能存的下
有人愿意写高精度吗???

幸运的是,这道题只关心那些是m的倍数,

我们其实只需要计算m的唯一分解式中各素因子在C(n-1,i)中的指数即可完成判断

这些指数可以通过C(n,k)=C(n,k-1)*(n-k+1)/k 递推得到

注意:

不能直接递推每个系数除以m的余数,因为%m意义下逆元不一定存在

题目思路分析到这里就结束了,说着简单,实际上代码中有很多细节:

  • 首先我们需要筛出素数,筛多少呢
    因为m<=1e9,所以我们筛到sqrt(m)=32000即可

  • 接下来是对于m的预处理
    我们需要把m分解一下

void prepare()
{
    for (int i=1;i<=tot&&sshu[i]<=m;i++)
    {
        if (m%sshu[i]==0) 
        {
            tt++;
            yue[tt]=sshu[i];    
        }
        while ((m%sshu[i]==0)&&(m/=sshu[i]))
            mm[tt]++;
    }
    //在这之前都是很正常的分解
    if (m!=1)      //如果sqrt(m)的质数内都没有m的约数,说明剩下的部分一定是一个大质数
    {
        tt++;      //我们需要把ta加入,不能忽略,因为这牵扯到之后是否整除的判断
        yue[tt]=m;
        mm[tt]=1;
    } 
}
  • 核心部分:判断是否整除
这一部分的细节超多

看一下这句话:if (fj(n-i,i))
表示我们要在分解式上*(n-i)/i
为什么是这个数呢?
因为第i+1个数的系数是:C(n-1,i)
C(n-1,i+1)=C(n-1,i)*((n-1)-i+1)/i
(因为m最小值是2,而a1和an的系数一定是1,所以我们直接不考虑)

在分解的过程中,
为了节约时间,我们只枚举m的素因子,
我们直接在m的分解式上做文章,
如果能整除,那么C(n-1,i)的质因数的次数一定是大于m的次数
这样减完了之后,mm[k]<=0

int fj(int x,int y)
{
    bool ff=1;
    for (int i=1;i<=tt;i++)
    {
        while ((x%yue[i]==0)&&(x/=yue[i])) mm[i]--; 
        while ((y%yue[i]==0)&&(y/=yue[i])) mm[i]++; 
        if (mm[i]>0) ff=0;          //一开始我写的是return 0,结果狂WA不止
//原因在于*x/y的操作一定要处理完再返回,如果中途return,就有可能使操作被跳过,影响之后的判断
    }
    return ff;                      //
}

for (int i=1;i<n;i++)
    if (fj(n-i,i))      //可以整除m 
    {
        ans[0]++;
        ans[ans[0]]=i+1;
}
  • 最后是出答案的时候
注意格式
//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=32005;
int sshu[N],tot=0,n,m;
int ans[100010],mm[30],yue[30],tt=0;
bool no[N];

void prime()
{
    memset(no,0,sizeof(no));
    for (int i=2;i<N;i++)
    {
        if (!no[i]) sshu[++tot]=i;
        for (int j=1;j<=tot&&sshu[j]*i<N;j++)
        {
            no[sshu[j]*i]=1;
            if (i%sshu[j]==0) break;
        }
    }
}

int fj(int x,int y)
{
    bool ff=1;
    for (int i=1;i<=tt;i++)
    {
        while ((x%yue[i]==0)&&(x/=yue[i])) mm[i]--;
        while ((y%yue[i]==0)&&(y/=yue[i])) mm[i]++;
        if (mm[i]>0) ff=0;                            //一开始我写的是return 0,结果狂WA不止
        //原因在于*x/y的操作一定要处理完再返回,如果中途return,就有可能使操作被跳过,影响之后的判断
    }
    return ff;
}

void prepare()
{
    for (int i=1;i<=tot&&sshu[i]<=m;i++)
    {
        if (m%sshu[i]==0) 
        {
            tt++;
            yue[tt]=sshu[i];    
        }
        while ((m%sshu[i]==0)&&(m/=sshu[i]))
            mm[tt]++;
    }
    if (m!=1)
    {
        tt++;
        yue[tt]=m;
        mm[tt]=1;
    } 
}

int main()
{
    prime();
    while (scanf("%d%d",&n,&m)!=EOF)
    {
        memset(mm,0,sizeof(mm));
        memset(yue,0,sizeof(yue));
        ans[0]=0; tt=0;

        prepare();  

        for (int i=1;i<n;i++)
            if (fj(n-i,i))      //可以整除m 
            {
                ans[0]++;
                ans[ans[0]]=i+1;
            }

        printf("%d\n",ans[0]);
        if (ans[0]!=0)
        {
            printf("%d",ans[1]);
            for (int i=2;i<=ans[0];i++) printf(" %d",ans[i]);
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值