[数论]Irrelevant Elements, ACM/ICPC NEERC 2004, Uva1635

[唯一分解定理在组合数学中的简单应用]
题意:给定n个数a1,a2…an,依次求出相邻两个数之和,将得到一个新数列。重复这个操作,最终将得到一个数。问这个数除以m的余数与原数列中的哪个数无关。

题解:最后的求和显然是a1,a2…an的一个线性组合。设ai的系数为f(i),那么当且仅当f(i)是m的倍数时,最终结果除以m的余数与ai无关。
此时这个问题变成:找出最终的线性组合中哪些项的系数是m的倍数。
题目中给出的操作是不断将相邻的两个数相加,每一层的数都可以表示为上一层两个相邻数的和,显然这是一个杨辉三角,而它的性质就是第n行的第i个数为C(n-1,i-1)。
此时这个问题变成:对于给定的一个整数n,找出C(n-1,0)到C(n-1,n-1)这n个数中有哪些数是m的倍数。
理论上可以利用C(n,k)=C(n,k-1)*(n-k+1)/k递推出所有的组合数,但n的范围1e5,只能使用高精度。这时需要注意到,本题只是要判断哪些是m的倍数,并不要求求出所有的组合数。这里利用数论中的唯一分解定理,依次计算m的唯一分解式中各个素因子在C(n-1,i-1)中的指数即可完成判断,而对组合数进行判断时仍然可以使用上述公式进行递推。
还需注意的是,本题判断m的倍数不能采取递推取模的方法。原因是递推式中含有除法,而模m意义下的逆元只有在与m互质时才存在。

//80ms
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxp 32001
#define maxn 100050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

int n, m, im, ans, en;
int prime[maxp], pnum = 0, pm[20], em[20];
bool flag[maxp], vis[maxn];

void getpri()
{
    memset(flag, 0, sizeof(flag));
    int p = sqrt(maxp + 0.5);
    for (int i = 2; i <= p; i++)
    {
        for (int j = i*i; j < maxp; j += i)
            flag[j] = 1;
    }
    for (int i = 2; i < maxp; i++)
    {
        if (!flag[i])
            prime[pnum++] = i;
    }
}

void init()
{
    memset(pm, 0, sizeof(pm));
    memset(em, 0, sizeof(em));
    im = 0;
    for (int i = 0; i < pnum&&prime[i] <= m; i++)
    {
        if (m%prime[i] == 0)
        {
            pm[im] = prime[i];
            while ((m%prime[i] == 0) && (m /= prime[i]))
                em[im]++;
            im++;
        }
        if (n == 0 || n == 1)
            break;
    }
    if (m > 1)
    {
        pm[im] = m;
        em[im++] = 1;
    }
}

bool getfac(int x, int y)
{
    bool cnt = true;
    for (int i = 0; i < im; i++)
    {
        while (x%pm[i] == 0 && (x /= pm[i]))
            em[i]--;
        while (y%pm[i] == 0 && (y /= pm[i]))
            em[i]++;
        if (em[i] > 0)
            cnt = false;
    }
    return cnt;
}

int main()
{
    getpri();
    while (scanf("%d%d", &n, &m) != EOF)
    {
        init();
        ans = en = 0;
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= n; ++i)
        {
            if (getfac(n - i, i))
            {
                vis[i + 1] = true;
                ans++;
                en = i + 1;
            }
        }
        printf("%d\n", ans);
        if (ans)
        {
            for (int i = 1; i < en; i++)
            {
                if (vis[i])
                    printf("%d ", i);
            }
            printf("%d", en);
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值