【P4370】[Code+#4]组合数问题2

Description

Link

给定 \(n,m\)\(k\) 个组合数 \(\dbinom{n}{m}\),使得结果最大。

\(1 \leq n \leq 10 ^ 6,1 \leq k \leq 10 ^ 5\)

Solution

众所周知,组合数和杨辉三角有着密不可分的联系,这就是一个杨辉三角 :

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

根据这个性质,我们发现,最大的数是 \(\dbinom{n}{n / 2}\),但在他的周围,我们却不能得到取哪 \(k\) 个数字能达到最大。

还有一个问题,那就是 \(\dbinom{n}{m}\) 太大了,我们没法存,并且我们也没法取模,因为取模之后就不能进行比较了。

在这里有一个很巧妙的方法解决了这个问题,那就是取对数。

根据高一学到的知识,对数有以下性质 :

\[\begin{aligned} \log(x \times y) &= \log(x) + \log(y) \\ \log(\dfrac{x}{y}) &= \log(x) - \log(y) \end{aligned} \]

之后根据组合数公式 \(\dbinom{n}{m} = \dfrac{n!}{m!(n - m)!}\)

所以一个组合数的对数就变成了这样的形式 :

\[\log\left(\dbinom{n}{m}\right) = \sum_{i = 1} ^ n \log(i) - \sum_{i = 1} ^ m\log(i) - \sum_{i = 1} ^ {n - m} \log(i) \]

所以我们预处理出 \(\sum_{i = 1} ^ n \log(i)\),之后 \(O(1)\) 查询就行了。

之后就是取数的问题了,有一个结论 :

\[\dbinom{n_1}{m} < \dbinom{n_2}{m} (n_2 > n_1) \]

根据上面的杨辉三角就可以推出来,对于每一列的数,从下到上,依次减小,所以肯定是 \(\dbinom{n}{m}\) 取了之后再取 \(\dbinom{n - 1}{m}\)

所以我们可以直接将最后一排全部扔进一个小根堆里面,运用贪心的思想,不断取最顶上的数 \(X\),去完之后再把 \(\dbinom{X - 1}{m}\) 给丢进堆里。

对于从顶上取出来的数直接用阶乘 + 逆元的方法预处理,直接算出来即可。

注意 : 为了不爆精度,必须用 double 来存 \(\log\) 值。

#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define int long long
using namespace std;
struct Node{
  int nn;
  int mm;
  double LLog;
  inline bool operator < (const Node &z) const {
    return LLog < z.LLog;//重定义小根堆
  }
};
priority_queue <Node> qp;
const int Maxk = 1e6 + 10;
const int mod = 1e9 + 7;
int n,k;
int a[Maxk];
double Log[Maxk];
int inv[Maxk];
inline int read()
{
	int s = 0, f = 0;char ch = getchar();
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
	return f ? -s : s;
}
void Prepare()
{
  inv[1] = inv[0] = 1,a[0] = 1;
  for(int i = 1;i <= 1e6;i ++) Log[i] = Log[i - 1] + log(i);//取 log  
  for(int i = 1;i <= 1e6;i ++) a[i] = (a[i - 1] % mod * i % mod + mod) % mod;// 阶乘 
  for(int i = 2;i <= 1e6;i ++) inv[i] = (mod - mod / i) * inv[mod % i] % mod;//逆元 
  for(int i = 1;i <= 1e6;i ++) inv[i] = inv[i - 1] * inv[i] % mod;//阶乘的逆元 
}
signed main()
{
  n = read(),k = read();
  Prepare();
  for(int i = 0;i <= n;i ++) {
    Node cur;
    cur.nn = n;
    cur.mm = i;
    cur.LLog = Log[n] - Log[i] - Log[n - i];
    //cout << cur.nn << " " << cur.mm << " " << cur.LLog << endl;
    qp.push(cur);
  }
  int Ans = 0;
  for(int i = 1;i <= k;i ++) {
    Node now = qp.top();
    qp.pop();
    Ans += (a[now.nn] % mod * inv[now.mm] % mod * inv[now.nn - now.mm] % mod + mod) % mod;
    //cout << "ANS :: " << Ans << " NOW.n ::" << now.nn << " NOW.m :: " << now.mm << " AAA :: " << now.LLog << endl;
    if(Ans >= mod) Ans %= mod;
    now.nn -= 1;//放入下一个数
    now.LLog = Log[now.nn] - Log[now.mm] - Log[now.nn - now.mm];
    qp.push(now);
  }
  cout << Ans << endl;
  return 0; 
} 
posted @ 2021-05-09 20:19  Ti_Despairy  阅读(40)  评论(0编辑  收藏  举报