牛客IOI周赛18-提高组 C

山脉

https://ac.nowcoder.com/acm/contest/7225/C

分析

这题是一个经典的组合数学的题。先说题解的证明我是看不会的,以下给出一个较为简便的理解。先说明不下降和不上升子序列没有本质区别,只需要讨论一种即可。

简化问题

给你一个长度为 的序列,要求每个元素 ,求不下降子序列的方案数。先考虑到对于一个一个集合 一定只对应一个序列。那么我们转化一下 表示 这个数在集合中出现的次数,那么 就可以表示一个序列,其中 。现在就是求问这个集合有多少种方案。这个可以考虑插板法,现在等同于给你 一个长度为 元素全为的 的序列,两个板之间的 的个数就是 的大小。因为左右两个端点必须有个板的。所以 。可以看作 个位置中要挑出 的方案数。

原问题

这个问题其实和上面的问题没有本质区别,只需要考虑上限是谁,枚举上限,再枚举前一部分选多少个之后,根据范德蒙德卷积公式化简,可得 。只需要预处理阶乘的逆元就好了,时间复杂度为

代码

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL read() {
    LL x = 0,f = 0;char ch = getchar();
    while(!isdigit(ch)) {if(ch=='-')f=1;ch = getchar();}
    while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
    return f ? -x : x; 
} 
const int N = 2e7 + 100,M = 1e7,P = 1e9 + 7;
int qpow(int a,int b) {
    LL x = 1;for(;b;b>>=1,a=1LL*a*a%P) 
    if(b&1) x = 1LL *  x * a % P;return x;
}
int n,m,pre[N],inv[N];
int C(int a,int b) {return 1LL * pre[b] * inv[a] % P * inv[b-a] % P;}
int main()
{
    pre[0] = 1;
    for(int i = 1;i <= 2*M+1;i++) pre[i] = 1LL * pre[i-1] * i % P;
    inv[2*M+1] = qpow(pre[2*M+1],P-2);
    for(int i = 2*M;i >= 0;i--) inv[i] = 1LL * inv[i+1] * (i + 1) % P;
    int T = read();
    while(T--) {
        n = read();m = read();
        LL ans = 0;
        for(int i = 0;i < n;i++) {
            ans = (1LL * ans + C(m-1,m+2*i-1)) % P;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
全部评论

相关推荐

09-29 11:19
门头沟学院 Java
点赞 评论 收藏
分享
4 1 评论
分享
牛客网
牛客企业服务