算法马拉松25 二分答案(组合数新高度!!!无敌的降维)

题目传送门

lyk最近在研究二分答案类的问题。

对于一个有n个互不相同的数且从小到大的正整数数列a(其中最大值不超过n),若要找一个在a中出现过的数字m,一个正确的二分程序是这样子的:
 

最终a[r]一定等于m。
但是这个和谐的程序被熊孩子打乱了。
熊孩子在一开始就将a数组打乱顺序。(共有n!种可能)
lyk想知道最终r=k的期望。
由于小数点非常麻烦,所以你只需输出将答案乘以n!后对1000000007取模就可以了。

在样例中,共有2个数,被熊孩子打乱后的数列共有两种可能(1,2)或者(2,1),其中(1,2)经过上述操作后r=1,(2,1)经过上述操作后r=0。r=k的期望为0.5,0.5*2!=1,所以输出1。
Input
3个整数n,m,k(1<=m<=n<=10^9,0<=k<=n)。
Output
一行表示答案
Input示例
2 1 1
Output示例
1

题解:

题目中的期望*n!可以转化成存在多少方案,使得r=k。
要使得r=k,我们仍可以通过二分来解决这个问题。
假设此时mid>k,则必须存在a[mid]>m,否则必须存在a[mid]<=m。
那么题目就相当于了存在lgn个这样的限制的数。
对于所有限制,我们可以通过乘法原理将它们的方案总数算出来。
对于没有限制的数,它们的方案总数其实就是一个数的阶乘。
这个阶乘我们可以通过打表求出来。
在程序中打出(1e7)!,(2e7)!,...,(1e9)!。每次在计算时只需再计算不超过1e7次就可以了。

这道题的思路很好像,就是根据二分做,然后求组合数,,,但是组合数就是一直算不对,,,卢卡斯学的太垃圾,。。但是n这么大,直接卢卡斯都不行,必须分块做,把大数的阶乘分块打表,每个块(1*1e6)然后就可以降维了。。。。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f;
const ll INFF=0x3f3f3f3f3f3f3f3f;
const double pi=acos(-1.0);
const double eps=1e-9;
const ll mod=1e9+7,blk=5000000;
ll quick_pow(ll a, ll b, ll MOD) {
    a = a % MOD;
    ll ans = 1;
    while (b > 0) {
        if (b & 1) {
            ans = ans * a % MOD;
        }
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}
const int lst[201]={1,974067448,682498929,598816162,491101308,586350670,76479948,463847391,723816384,172827403,67347853,407719831,27368307,606322308,625544428,1669644,199888908,534491822,888050723,884343068,927880474,112249297,281863274,770511792,661224977,935080803,623534362,797848181,970055531,232253360,261384175,659224434,195888993,509096183,66404266,785113347,547665832,996122673,109838563,34538816,933245637,911398531,724691727,114985663,368925948,464456846,268838846,938269070,136026497,564758715,112390913,167240465,135498044,889410460,217544623,996097969,419363534,607730875,500780548,651081062,668123525,563432246,128487469,318951960,30977140,93940075,522049725,559947225,309058615,624577416,386027524,716986954,189239124,179518046,148528617,529726489,940567523,473718967,917084264,105419548,429277690,937409008,996164327,418537348,358655417,775305697,568392357,392838702,780072518,596737944,462639908,843321396,275105629,432030917,909210595,321685608,99199382,5003231,703397904,863250534,733333339,180898306,97830135,864869025,608823837,46819124,256141983,491415383,141827977,688809790,696628828,457469634,637939935,502297454,811575797,48053248,848924691,26011548,131772368,271198437,724464507,510650790,272814771,564188856,326159309,44135644,456152084,816929577,903466878,218107212,92255682,196345098,769795511,407518072,373745190,147050765,606241871,666493603,825871994,561011609,957939114,765215899,435887178,201339230,852304035,469928208,663307737,414236650,375297772,211487466,217598709,676526196,624148346,439411911,671734977,644050694,624500515,715264908,748510389,819801784,203191898,340030191,423951674,331910086,629786193,341080135,672850561,684748812,814362881,60625018,823845496,175638827,116667533,163928347,256473217,393556719,627655552,687265514,245795606,36292292,586445753,953634340,172114298,260466949,193781724,756154604,778983779,49031023,83868974,954913,315103615,346966053,965785236,321900901,492741665,532702135,377329025,847645126,847549272,3258987,698611116};
ll Fact(ll x){
    if(x<0) return 0;
    ll res=lst[x/blk];
    for(ll i=(x/blk)*blk+1;i<=x;i++) res=(res*i)%mod;
    return res;
}
ll C(ll n, ll m, ll MOD) {
    if(n<0||m<0||n<m) return 0;
    return Fact(n) * quick_pow(Fact(n - m), MOD - 2, MOD) % MOD ;
}
int main()
{
    ll n,m,k;
    scanf("%lld%lld%lld",&n,&m,&k);
    ll l=1,r=n;
    ll num1=0,num2=0;
    while(r>=l)
    {
        ll mid=(l+r)/2;
        if(mid<=k){l=mid+1;num1++;}
        else{r=mid-1;num2++;}
    }
    printf("%lld\n",(C(m,num1,mod)*C(n-m,num2,mod)%mod*Fact(n-num1-num2))%mod);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值