• [luogu p2282] 组合数问题


    传送门

    组合数问题

    题目描述

    组合数 (inom{n}{m}) 表示的是从 (n) 个物品中选出 (m) 个物品的方案数。举个例子,从 ((1,2,3)) 三个物品中选择两个物品可以有 ((1,2),(1,3),(2,3)) 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 (inom{n}{m}) 的一般公式:

    [inom{n}{m}=frac{n!}{m!(n-m)!} ]

    其中 (n!=1 imes2 imescdots imes n);特别地,定义 (0!=1)

    小葱想知道如果给定 (n,m)(k),对于所有的 (0leq ileq n,0leq jleq min left ( i, m ight )) 有多少对 ((i,j)) 满足 (k|inom{i}{j})

    输入输出格式

    输入格式

    第一行有两个整数 (t,k),其中 (t) 代表该测试点总共有多少组测试数据,(k) 的意义见问题描述。

    接下来 (t) 行每行两个整数 (n,m),其中 (n,m) 的意义见问题描述。

    输出格式

    (t) 行,每行一个整数代表所有的 (0leq ileq n,0leq jleq min left ( i, m ight )) 中有多少对 ((i,j)) 满足 (k|inom{i}{j})

    输入输出样例

    输入样例 #1

    1 2
    3 3
    

    输出样例 #1

    1
    

    输入样例 #2

    2 5
    4 5
    6 7
    

    输出样例 #2

    0
    7
    

    说明

    【样例1说明】

    在所有可能的情况中,只有 (inom{2}{1} = 2) 一种情况是 (2) 的倍数。

    【子任务】

    • 对于全部的测试点,保证 (0 leq n, m leq 2 imes 10^3)(1 leq t leq 10^4)

    分析

    这题一看就要用 (operatorname{O}(n ^ 2)) 预处理 (operatorname{O}(1)) 回答的算法,那么具体怎么做呢?

    首先用杨辉三角预处理出2000内所有的组合数对 (k) 取模的结果。如下:

    void init(int k) {
        c[0][0] = c[1][0] = c[1][1] = 1;
        for (int i = 2; i < maxn; ++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;
            }
        }
    }
    

    然后仔细观察题目要求什么:

    对于所有的 (0leq ileq n,0leq jleq min left ( i, m ight )) 有多少对 ((i,j)) 满足 (k|inom{i}{j})

    为了方便讨论,若 (m > n),我们一律令 (n ightarrow m)

    定义 (ans_{i, j}) 为答案数组,用二维前缀和得到公式:

    [ans_{i, j} = ans_{i - 1, j} + ans_{i, j - 1} - ans_{i - 1, j - 1} ]

    当然,如果我们发现 (k|inom{i}{j}),那么 (ans_{i, j}) 还需自增 (1)

    我们就可以写出这样的代码:

    void init(int k) {
        c[0][0] = c[1][0] = c[1][1] = 1;
        for (int i = 2; i < maxn; ++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;
                ans[i][j] = ans[i - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1];
                if (c[i][j] == 0) ++ans[i][j];
            }
        }
    }
    

    然而就这样结束了吗?

    如果这样,提交程序,你会发现你的代码能拿到15分的好成绩

    这是为什么呢?

    不妨来看看当 (k = 5) 时,ans的表如何。(此处列举了前10*10个)

    0MkJMj.png

    显然,绿色方块的值是红+蓝-紫。但是你发现没有,红色方块的所在位置已经超出了杨辉三角的预处理范围。(橙色左下方为预处理范围,右上角为非预处理范围),也就是当 (i = j) 时,(ans_{i - 1, j}) 没有被处理。

    但是,这个东西其实很好处理,因为 (ans_{i+1, i} = ans_{i, i})。至于这是为什么,见其定义:

    对于所有的 (0leq ileq n,0leq jleq min left ( i, m ight )) 有多少对 ((i,j)) 满足 (k|inom{i}{j})

    观察到这个 (min(i, m)) 了吗?这个的意思就是说,当 (m > n) 时,(ans_{m, n})(ans _{n, n}) 其实是等价的。

    同样的道理便可以得到 (ans_{i+1, i} = ans_{i, i})。因此只要在i循环中最后加一句ans[i][i + 1] = ans[i][i];即可。代码如下:

    void init(int k) {
        c[0][0] = c[1][0] = c[1][1] = 1;
        for (int i = 2; i < maxn; ++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;
                ans[i][j] = ans[i - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1];
                if (c[i][j] == 0) ++ans[i][j];
            }
            ans[i][i + 1] = ans[i][i];
        }
    }
    
    

    这样就可以AC啦~上一个整体代码吧!

    代码

    /*
     * @Author: crab-in-the-northeast 
     * @Date: 2020-10-01 14:01:33 
     * @Last Modified by: crab-in-the-northeast
     * @Last Modified time: 2020-10-01 16:18:48
     */
    #include <iostream>
    #include <cstdio>
    
    const int maxn = 2005;
    const int maxm = 2005;
    long long c[maxn][maxm], ans[maxn][maxm];
    
    void init(int k) {
        c[0][0] = c[1][0] = c[1][1] = 1;
        for (int i = 2; i < maxn; ++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;
                ans[i][j] = ans[i - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1];
                if (c[i][j] == 0) ++ans[i][j];
            }
            ans[i][i + 1] = ans[i][i];
        }
    }
    
    int main() {
        int t, k;
        std :: scanf("%d %d", &t, &k);
        init(k);
        while (t--) {
            int n, m;
            std :: scanf("%d %d", &n, &m);
            std :: printf("%lld
    ", ans[n][m > n ? n : m]);
        }
        return 0;
    }
    

    评测记录

    评测记录

  • 相关阅读:
    C#基础--经常用到的技术点
    VS2012 快捷键使用
    单例模式以及使用在winform界面开发上的实例
    Linq to sql 增、删、查、改、解决更新过程冲突的方法
    Lambda表达式
    Hello LINQ
    基础记录--.NET开发规范
    WCF通信模式(转)
    Excel导入导出数据库
    ENS 域名注册表智能合约(ENSRegistry.sol)解析
  • 原文地址:https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p2282.html
Copyright © 2020-2023  润新知