@loj - 6353@「CodePlus 2018 4 月赛」组合数问题 2


@description@

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

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

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

sample input
2 3
sample output
4

对于 \(20\%\) 的数据,\(n\leq 10\)
对于 \(40\%\) 的数据,\(n\leq 500\)
对于另外 \(20\%\) 的数据,\(k=1\)
对于 \(100\%\) 的数据, \(1\leq n\leq 10^6,1\leq k\leq 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}^n\log_2 i-\sum_{i=1}^m\log_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。

posted @ 2019-01-02 23:10  Tiw_Air_OAO  阅读(283)  评论(0编辑  收藏  举报