首页 > 其他分享 >P3726 抛硬币

P3726 抛硬币

时间:2023-10-25 19:13:36浏览次数:35  
标签:return dbinom limits 硬币 sum P3726 pk mod

传送门

description

给定 \(n,m,k\),任意两个长度分别为 \(n,m\) 的 01 串,如果前者所含的 1 的数量严格大于后者,则这两个 01 串是强的,求有多少组强的 01 串,答案模 \(10^k\)

  • \(n,m\leq 10^{15}\)

  • \(m\leq n\leq m+10^4\)

  • \(1\leq k\leq 9\)

  • 十测

solution

枚举两个 01 串分别含有多少个 1,答案为 \(\sum\limits_{j=0}^m \sum\limits_{i=j+1}^n \dbinom{m}{j}\dbinom{n}{i}\)。平移 \(i\) 的枚举范围得 \(\sum\limits_{j=0}^m\sum\limits_{i=1}^{n-j} \dbinom{m}{j}\dbinom{n}{i+j}\)。可以将 \(i\) 的枚举上界扩充到 \(n\),式子值不变,并交换求和顺序得 \(\sum\limits_{i=1}^n\sum\limits_{j=0}^m\dbinom{m}{j}\dbinom{n}{i+j}\),把 \(\dbinom{m}{j}\) 换成 \(\dbinom{m}{m-j}\),然后再应用范德蒙德卷积公式得 \(\sum\limits_{i=1}^n \dbinom{n+m}{m+i}=\sum\limits_{i=m+1}^{n+m}\dbinom{n+m}{i}\)。

我们需要较快地求出这个式子在模 \(10^k\) 意义下的值。

对于单个组合数,我们可以使用 exLucas 定理,\(O(2^k+5^k)\) 预处理,\(O(\log(2^k+5^k))\) 求值。但是这个式子的枚举范围是 \(O(n)\) 的,不能接受。

一个十分巧妙的变换是,注意到 \(O(\dfrac{n+m}{2}-m)=O(\dfrac{n-m}{2})\) 并不是很大,而我们应用组合数的性质 \(\sum\limits_{i=0}^n \dbinom{n}{i}=2^n\) 容易得出 \(\sum\limits_{i=\lfloor\frac{n+m}{2}\rfloor}^{n+m} \dbinom{n+m}{i}\) 的值,我们只需要暴力算一下 \(\sum\limits_{i=\lfloor\frac{n+m}{2}\rfloor}^{m} \dbinom{n+m}{i}\) 的值就可以了。

时间复杂度 \(O(T(2^k+5^k+(n-m)\log (2^k+5^k)))\)

算一行一半组合数和的方法见代码。qwq

code

几乎最裂解的大常数代码

#include<bits/stdc++.h>

using namespace std;

typedef long long E;

inline E ksm(E a,E b,E mod){
  E ret=1;
  while(b){
    if(b&1) ret=ret*a%mod;
    a=a*a%mod;
    b>>=1;
  }
  return ret;
}

E exgcd(E a,E b,E &x,E &y){
  if(!b) return x=1,y=0,a;
  E d=exgcd(b,a%b,y,x);
  return y-=a/b*x,d;
}

inline E inv(E p,E mod){
  if(__gcd(p,mod)!=1) return -1;
  E x,y;
  E d=exgcd(p,mod,x,y);
  return (x%mod+mod)%mod;
}

vector<vector<E>> sum;
int idx;
unordered_map<E,int> id;
E calcu(E n,E p,E pk){ // calcu \frac{n!}{p^x} \bmod p^k (x is the max number s.t. p^x \mid n)
  if(!n) return 1;
  E t=calcu(n/p,p,pk);
  E u=1,L=n%pk,w=id[p]-1;
  u=u*sum[w][pk-1]%pk;
  t=t*sum[w][L]%pk;
  t=t*ksm(u,n/pk,pk)%pk;
  return t;
}

inline E solve1(E n,E m,E p,E pk){
  E u=calcu(n,p,pk),v=inv(calcu(m,p,pk),pk),w=inv(calcu(n-m,p,pk),pk);
  return u*v%pk*w%pk;
}

inline E get(E n,E p){
  E t=p,ans=0;
  while(t<=n){
    ans+=n/t;
    t*=p;
  }
  return ans;
}

vector<pair<E,E>> pd;
inline E crt(E n,E m){
  E M=1;
  for(auto u:pd) M*=u.second;
  E ans=0;
  for(auto u:pd){
    if(ksm(u.first,get(n,u.first)-get(m,u.first)-get(n-m,u.first),u.second)==0) continue;
    E t=solve1(n,m,u.first,u.second)*ksm(u.first,get(n,u.first)-get(m,u.first)-get(n-m,u.first),M)%M;
    ans=(ans+t*(M/u.second)%M*inv(M/u.second,u.second)%M)%M;
  }
  return ans;
}

void init(E p){
  sum.clear(); pd.clear(); idx=0;
  E x=p;
  for(int i=2; i*i<=x; i++){
    if(x%i) continue;
    E s=1;
    while(x%i==0) x/=i,s*=i;
    pd.emplace_back(make_pair(i,s));
    id[i]=++idx;
  }
  if(x>1){
    id[x]=++idx;
    pd.emplace_back(make_pair(x,x));
  }

  sum.resize(idx);
  for(int i=0; i<idx; i++){
    E P=pd[i].first;
    E s=1;
    sum[i].emplace_back(1);
    for(int j=1; j<=pd[i].second; j++){
      if(j%P) s=s*j%pd[i].second;
      sum[i].emplace_back(s);
    }
  }
}

E a,b,p;
void solve(){
  E tmp=1;
  while(p--) tmp*=10;
  p=tmp;
  tmp=log10(tmp);
  init(p); // exlucas 预处理

  E ans=0;

  /*
  for(E i=b+1; i<=a+b; i++){  暴力
    ans=(ans+crt(a+b,i));
  }
  cout<<ans%p<<endl;
  ans=0;*/

	/*
	
	当 n 为奇数时,2 不存在逆元,不好计算一个组合数的 1/2
	
	此时可以应用组合数的递推公式 C(m,n)=C(m,n-1)+C(m-1,n-1)
	
	变成求上一行(一定是偶数)的一半的和,就好做了
	
	*/

  E L=(a+b)/2;
  if((a+b+1)&1){
    ans=ksm(2,a+b-1,p)+crt(a+b-1,L-1);
    ans%=p;
  }
  else{
    ans=ksm(2,a+b-1,p);
    L++;
  }
  
  if(b+1>L){
    for(E i=L; i<=b; i++){
      ans=(ans-crt(a+b,i))%p;
    }
  }
  else{
    for(E i=b+1; i<L; i++){
      ans=(ans+crt(a+b,i))%p;
    }
  }
  ans=(ans%p+p)%p;
  printf("%0*lld\n",tmp,ans); // 讨论区看的输出技巧()

}

int main(){

  while(cin>>a>>b>>p) solve();

  return 0;
}

标签:return,dbinom,limits,硬币,sum,P3726,pk,mod
From: https://www.cnblogs.com/FreshOrange/p/17787916.html

相关文章

  • 【LeetCode】LCP 06.拿硬币
    描述桌上有n堆力扣币,每堆的数量保存在数组coins中。我们每次可以选择任意一堆,拿走其中的一枚或者两枚,求拿完所有力扣币的最少次数。示例输入:[4,2,1]输出:4解释:第一堆力扣币最少需要拿2次,第二堆最少需要拿1次,第三堆最少需要拿1次,总共4次即可拿完。限制1<=n<=41<=co......
  • 【洛谷 8597】 [蓝桥杯 2013 省 B] 翻硬币
    #[蓝桥杯2013省B]翻硬币##题目背景小明正在玩一个“翻硬币”的游戏。##题目描述桌上放着排成一排的若干硬币。我们用`*`表示正面,用`o`表示反面(是小写字母,不是零),比如可能情形是`**oo***oooo`,如果同时翻转左边的两个硬币,则变为`oooo***oooo`。现在小明的问题是:如果......
  • #9134. 翻转硬币 题解
    首先考虑一些简单的情况,比如\(m=1\)。容易发现操作1和操作2的顺序不会影响结果,于是可以钦定所有操作1在操作2之前。并且可以发现,进行完所有1后2的次数即为\((\text{连续段个数}-1)\)。然后考虑将\(m>1\)的情况。显然最后序列上每\(m\)长度分一段,则每一段都相......
  • 有趣的概率——车羊问题与硬币问题
      1、经典车羊问题假设你参加一个游戏节目,有三扇关闭的门,其中一扇后面有一辆汽车,而其他两扇后面是山羊。你首先选择一扇门,然后主持人打开另外两扇门中的一扇,露出其中一只山羊。现在,你可以选择是否改变自己的选择,选择另外一扇未被打开的门。那么,应该改变选择还是保持原来的选......
  • 力扣-LCP 06-拿硬币
    桌上有n堆力扣币,每堆的数量保存在数组coins中。我们每次可以选择任意一堆,拿走其中的一枚或者两枚,求拿完所有力扣币的最少次数。示例1:输入:[4,2,1]输出:4解释:第一堆力扣币最少需要拿2次,第二堆最少需要拿1次,第三堆最少需要拿1次,总共4次即可拿完。示例2:输入:[2,3,10......
  • 【LeetCode】LCP 06. 拿硬币
    描述桌上有​​n​​​堆力扣币,每堆的数量保存在数组​​coins​​中。我们每次可以选择任意一堆,拿走其中的一枚或者两枚,求拿完所有力扣币的最少次数。示例输入:​​[4,2,1]​​输出:​​4​​解释:第一堆力扣币最少需要拿2次,第二堆最少需要拿1次,第三堆最少需要拿1次,......
  • 【lc】441. 排列硬币
    链接https://leetcode.cn/problems/arranging-coins/description/问题分析这题看数据规模,遍历肯定搞不定。看数据规律,我们优先考虑二分。然后单拎出来一个函数用来计算求和即可。其中,二分如果不好判断边界,就假定极限情况(来到了left==right的情况),看看最后你要的值是left还......
  • B3635 硬币问题
    B3635硬币问题方法一:搜索#include<bits/stdc++.h>usingnamespacestd;intm;intdfs(intn){//求凑够n元的最小硬币数 if(n<=4&&n>=1)returnn; if(n>=5&&n<=9)returnn-4; if(n>=10&&n<=11)return12-n; ret......
  • 蓝桥杯省赛真题(砍树 整数删除 景区导游 翻转硬币)
    蓝桥杯省赛真题(砍树整数删除景区导游翻转硬币)四道比较难的题(题解是官方提供的)砍树(树上差分)https://www.lanqiao.cn/problems/3517/learning/解题思路在这个问题中,我们需要找到一条边,砍掉它之后,所有给出的节点对\((a_i,b_i)\)之间都不再连通。换言之,这条边应是所有\((......
  • Python用 PyMC3 贝叶斯推理案例研究:抛硬币和保险索赔发生结果可视化
    全文链接:https://tecdat.cn/?p=33416原文出处:拓端数据部落公众号介绍在这里,我们将帮助客户将PyMC3用于两个贝叶斯推理案例研究:抛硬币和保险索赔发生。方法:回想一下,我们最初的贝叶斯推理方法是:设置先前的假设,并根据启发式、历史或样本数据建立我们数据的“已知已知”。形......