BZOJ 4403: 序列统计 (组合数 Lucas 数论推导)

BZOJ 4403: 序列统计

Time Limit: 3 Sec Memory Limit: 128 MB
Description

给定三个正整数N、L和R,统计长度在1到N之间,元素大小都在L到R之间的单调不降序列的数量。输出答案对10^6+3取模的结果。

Input
输入第一行包含一个整数T,表示数据组数。
第2到第T+1行每行包含三个整数N、L和R,N、L和R的意义如题所述。
1≤N,L,R≤10^9,1≤T≤100,输入数据保证L≤R。
Output
输出包含T行,每行有一个数字,表示你所求出的答案对10^6+3取模的结果。

Sample Input
2
1 4 5
2 4 5
Sample Output
2
5

思路:
一道标准的排列组合题目,不过思维很是巧妙,辣鸡的我困扰了许久。
这道题目我们很容易想到一位一位的去考虑,相邻位数之间的递推关系不难发现,但让人崩溃的是1e9的数据规模,递推肯定挂得死死的。虽然无奈但是我们还是确定了思路,一个式子解决问题!
然后。。。额,活生生被搞成了一道数学题。
进过多次尝试后,我绝望地发现,好像并没有什么公式或方法可以直接解决这一问题,那么还是只能对于1~n中的一个i进行分析。我们发现无论我们选出来哪一些数,针对于这些数,有且只有一种排列方式让它满足单调不降(相同数之间交换认为是同一种)。
所以就成了组合问题。如果是单纯的C(m,i),并不包含一种元素可以多选的情况,那如果我们每个元素都取i个呢?->C(m*i,i)显然也是有问题的。eg:2 4 5这一组数据,本来是(4 5)只有4,5 4,4 5,5,现在变成了(4 4 5 5)有4,4 4,5 4,5 4,5 4,5 5,5。为什么呢?因为我们的组合数把两个4,当做了不同的两个数,就多了很多重复的情况。所以说如果我们要添加元素的话,只能增加有数字重复的状态,而不能增加原有的状态。那么我们考虑不去添加数,而是去添加一些符号,来表示这些重复的数字。eg:还是2 4 5这一组数据,我们添加一个+号,来表示它是某个数后边第一个和它相同的数, 4,+ 就代表4,4 ; 5,+就代表5,5;那么我们就有了三个元素(4 5 +);方案就有4,5;4,+;5,+三种,这种方法既解决了有数字重复的情况,又不影响没有数字的情况。
找到了解决方法,做普遍推广,对于i位数,我们添加i-1个符号(最多有i个重复数字),所以方案数就是C(m+i-1,i)。
现在只剩下sigma的问题了,(当然不能for一遍sum)。因为C(m,n)=C(m-1,n)+C(m-1,n-1),在m个东西中取n个,可以分成两类,【(1)不取第一个,那么就是在剩下的m-1个里面取n个。(2)取了第一个,那么就是在剩下的m-1个里面取n-1个。】
我们要求的答案就是,C(m+n-1,n)+C(m+n-2,n-1)+…+C(m+1,2)+C(m,1);
如果我们在式子的末尾添上一个C(m,0);
原式就变成了C(m+n-1,n)+C(m+n-2,n-1)+…+C(m+1,2)+C(m,1)+C(m,0);
因为C(m,1)+C(m,0)=C(m+1,1);
所以原式=C(m+n-1,n)+C(m+n-2,n-1)+…+C(m+1,2)+C(m+1,1);
又因为C(m+1,1)+C(m+1,2)=C(m+2,2);
所以原式=C(m+n-1,n)+C(m+n-2,n-1)+…+C(m+2,2);
…最终ans就是C(m+n,n);
因为我们加上了一个C(m,0);所以最后减掉一个1。
我们求C(m+n,n) - 1!完美!!
然后就是Lucas,逆元求组合数了。

注意一下,因为我们最后要-1,ans可能为负,所以我们做一个处理 ( ans ) % mod + mod ) % mod 。

如果下面的操作还有不懂的话请转至组合数

#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;

const LL mod = 1e6 + 3;

LL mub[mod+10];
LL x, y;

void init(){//初始化阶乘..超过mod
    mub[0] = 1;//注意细节
    for(int i=1; i<=mod+5; i++){
        mub[i] = mub[i-1] * i % mod;
    }
}

LL exgcd(LL a, LL b, LL &x, LL &y){//扩展欧几里得求逆元
    if(a == 0 && b == 0)
        return -1;
    if(b == 0){
        x = 1; y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x);
        y -= a / b * x;
    return d;
}

LL mod_reverse(LL a, LL n){
    LL d = exgcd(a, n, x, y);
    if(d == 1)
        return ( x % n + n ) % n;
    else
        return -1;
}

LL solve(LL a, LL b){
    if(a > b) return 0;//
    LL nn = mod_reverse((mub[a] * mub[(b + mod - a) % mod]) % mod, mod);
    return mub[b] * nn % mod;
}

void to_solve(LL a, LL b){//Lucas
    if(b < mod){
        solve(a, b);
        return;
    }
    printf("%lld\n", ( (solve(a/mod, b/mod) * solve(a%mod, b%mod) - 1 ) % mod + mod ) % mod );//
}

int main(){
    int T; scanf("%d", &T);
    init();
    while ( T-- ){
        LL n, l ,r;
        scanf("%lld%lld%lld", &n, &l, &r);
        LL m = r - l + 1;
        if(m + n < mod){
            printf("%lld\n", ( ( solve(n, m+n) - 1 ) % mod + mod ) % mod );//
        }
        else to_solve(n, m+n);
    }
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值