zoukankan      html  css  js  c++  java
  • @loj


    @description@

    请你找到 k 个不同的组合数,使得对于其中任何一个组合数 (C_a^b)(0leq bleq aleq n)。所谓不同的组合数,即对于组合数 (C_{a_1}^{b_1})(C_{a_2}^{b_2}) ,若 (a_1 eq a_2) 或者 (b_1 eq b_2) ,则我们认为这两个组合数是不同的。问这 (k) 个组合数的和最大是多少?

    input
    第一行两个整数 n, k。

    output
    一行一个整数,代表 k 个组合数的和对 10^9+7 取模之后的结果;数据保证一定有至少 k 个数可以选。

    sample input
    2 3
    sample output
    4

    对于 (20\%) 的数据,(nleq 10)
    对于 (40\%) 的数据,(nleq 500)
    对于另外 (20\%) 的数据,(k=1)
    对于 (100\%) 的数据, (1leq nleq 10^6,1leq kleq 10^5)

    @solution@

    问题相当于求前 k 大的组合数。

    (a < b < m/2)(a > b > m/2) 时,有 (C_{m}^a < C_m^{b})
    (a < b) 时,有 (C_{a}^p < C_{b}^p)
    这是可以从杨辉三角中看出来的。

    我们可以把最大的那个组合数 (C_n^{n/2}) 加入优先队列 ,然后向四周扩展。每一次从优先队列中取出最大值 (C_{a}^{b}),扩展出 (C_{a-1}^{b})(C_{a}^{b-1})(C_{a}^{b+1}),然后将它们加入优先队列。扩展 k 次即可。
    同时要注意不要重复经过某一个点。开一个 set 判一下重。

    那么问题来了:我们的组合数是取了模的,塞在优先队列里面怎么比较大小呢?逼我写高精度?
    这个时候,一个闻所未闻的操作就来了:两边同时取对数
    我们组合数公式长这样:

    [C_n^m=dfrac{n!}{m!*(n-m)!} ]

    取完对数长这样:

    [log_2 C_n^m=sum_{i=1}^nlog_2 i-sum_{i=1}^mlog_2 i-sum_{i=1}^{n-m}log_2 i ]

    (当然底数不一定为 2)
    可以发现这个式子是绝对不会溢出的,而且还可以前缀和预处理。
    又因为底数大于 1,所以对数的大小关系等同于原数的大小关系。
    所以我们就可以比较组合数之间的大小了。

    那么问题来了:double 的精度真的不会翻车吗_(:з」∠)_?

    @accepted code@

    #include<set>
    #include<cmath>
    #include<queue>
    #include<cstdio>
    #include<iostream>
    using namespace std;
    const int MOD = int(1E9) + 7;
    const int MAXN = 1000000;
    int fct[MAXN + 5], inv[MAXN + 5];
    double lgsum[MAXN + 5];
    int pow_mod(int b, int p) {
        int ret = 1;
        while( p ) {
            if( p & 1 ) ret = 1LL*ret*b%MOD;
            b = 1LL*b*b%MOD;
            p >>= 1;
        }
        return ret;
    }
    void init() {
        fct[0] = 1;
        for(int i=1;i<=MAXN;i++)
            fct[i] = 1LL*fct[i-1]*i%MOD;
        inv[MAXN] = pow_mod(fct[MAXN], MOD-2);
        for(int i=MAXN-1;i>=0;i--)
            inv[i] = 1LL*inv[i+1]*(i+1)%MOD;
        lgsum[1] = log2(1);
        for(int i=2;i<=MAXN;i++)
            lgsum[i] = lgsum[i-1] + log2(i);
    }
    int C(int n, int m) {
        return 1LL*fct[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    struct node{
        int n, m;
        node(int _n=0, int _m=0):n(_n), m(_m){}
    };
    bool operator < (node a, node b) {
        if( lgsum[a.n] - lgsum[a.m] - lgsum[a.n - a.m] != lgsum[b.n] - lgsum[b.m] - lgsum[b.n - b.m] )
            return lgsum[a.n] - lgsum[a.m] - lgsum[a.n - a.m] < lgsum[b.n] - lgsum[b.m] - lgsum[b.n - b.m];
        else return (a.n == b.n) ? a.m < b.m : a.n < b.n;
    }
    set<node>Set;
    priority_queue<node>que;
    int main() {
        int n, k, ans = 0; init();
        scanf("%d%d", &n, &k);
        node s = node(n, n/2);
        Set.insert(s), que.push(s);
        for(int i=1;i<=k;i++) {
            s = que.top(); que.pop();
            ans = (ans + C(s.n, s.m))%MOD;
            if( s.m != 0 ) {
                if( !Set.count(node(s.n, s.m - 1)) )
                    Set.insert(node(s.n, s.m - 1)), que.push(node(s.n, s.m - 1));
            }
            if( s.m != s.n ) {
                if( !Set.count(node(s.n, s.m + 1)) )
                    Set.insert(node(s.n, s.m + 1)), que.push(node(s.n, s.m + 1));
                if( !Set.count(node(s.n - 1, s.m)) )
                    Set.insert(node(s.n - 1, s.m)), que.push(node(s.n - 1, s.m));
            }
        }
        printf("%d", ans);
    }
    

    @details@

    重新刷新了 double 的精度问题。
    MD 每次我写二分你都卡我精度你这次偏偏不会卡这玩意儿的精度?

    取对数这种操作也不是没有见过,《麦森数》那道题就有用到。
    看来印象不深刻 QAQ……我果然还是太弱了 QAQ。

  • 相关阅读:
    Django: ModelForm中Meta的fields等成员介绍
    python的random函数
    设置mysql隔离级别
    ubantu 下 修改mysql 默认编码
    jdbc 模板 连接
    sql 注入 与解决
    jdbc 简单连接
    动态代理 例子
    自定义的一个数据输入类
    类加载器 读取配置文件
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10211877.html
Copyright © 2011-2022 走看看