zoukankan      html  css  js  c++  java
  • 【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}) 太大了,我们没法存,并且我们也没法取模,因为取模之后就不能进行比较了。

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

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

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

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

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

    [logleft(dbinom{n}{m} ight) = sum_{i = 1} ^ n log(i) - sum_{i = 1} ^ mlog(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; 
    } 
    
  • 相关阅读:
    log4net(c#) 配置及使用
    【转】JMeter试用手记
    【转】性能测试工具JMeter的使用技巧
    【转】JMeter基础之——录制脚本
    【转】Jmeter基础之——jmeter基础概念
    【转】JMeter基础之——一个简单的性能测试
    【转】JMeter入门
    【转】Jmeter压力测试模拟并发
    【转】JMeter Tutorial的安装和具体操作
    【转】JMeter代理录制脚本
  • 原文地址:https://www.cnblogs.com/Ti-despair/p/14748728.html
Copyright © 2011-2022 走看看