[AH2017/HNOI2017]抛硬币

本文介绍了如何解决AH2017/HNOI2017竞赛中的抛硬币问题,重点讲解了使用范德蒙德卷积优化计算的策略。通过对称性和部分分的方法,探讨了计算对称方案数S1和不对称方案数S2的思路,最终通过扩展卢卡斯定理和中国剩余定理求解,同时解决了模数为非质数的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、题目

点此看题

二、解法

前置芝士:范德蒙德卷积:
C ( n + m , k ) = ∑ i = 0 k C ( n , i ) C ( m , k − i ) C(n+m,k)=\sum_{i=0}^kC(n,i)C(m,k-i) C(n+m,k)=i=0kC(n,i)C(m,ki)这道题的暴力算式是很好写的,但是优化不动,因为用不到 a − b ≤ 10000 a-b\leq10000 ab10000,从最简单的情况考虑:

部分分:a=b

此时 a , b a,b a,b公平竞争,对于 a a a赢着的情况,一定严格对应一种 a a a输着的情况(把他们的输赢情况反转),那么我们考虑算出他们平局的情况,用所有情况减去除 2 2 2即是答案,平局算式如下:
∑ i = 0 a C ( a , i ) 2 = ∑ i = 0 a C ( a , i ) C ( a , a − i ) = C ( 2 a , a ) \sum_{i=0}^aC(a,i)^2=\sum_{i=0}^aC(a,i)C(a,a-i)=C(2a,a) i=0aC(a,i)2=i=0aC(a,i)C(a,ai)=C(2a,a)所以答案是:
2 a + b − C ( 2 a , a ) 2 \frac{2^{a+b}-C(2a,a)}{2} 22a+bC(2a,a)a>b

有点难,按照部分分的对应法则我们发现有些 a a a赢的情况反转时候还是赢的,我们称这种现象叫不对称,记对称的方案数是 S 1 S_1 S1,不对称的方案数是 S 2 S_2 S2,则有这样的关系:
S 1 + S 2 = 2 a + b , a n s = S 1 2 + S 2 S_1+S_2=2^{a+b},ans=\frac{S_1}{2}+S_2 S1+S2=2a+b,ans=2S1+S2我们只需要求出其中一个就行了,求对称的只能暴力,所以我们去算 S 2 S_2 S2,我们考虑此时满足什么样的关系,用形式上的表达,记 ∣ a ∣ |a| a a a a 1 1 1个数, ∣ b ∣ |b| b b b b 1 1 1个数:
∣ a ∣ > ∣ b ∣ , a − ∣ a ∣ > b − ∣ b ∣ |a|>|b|,a-|a|>b-|b| a>b,aa>bb也就是 a − b > ∣ a ∣ − ∣ b ∣ a-b>|a|-|b| ab>ab,这时候出现了 a − b a-b ab就很舒服了,盯准它,枚举 ∣ b ∣ |b| b ∣ a ∣ − ∣ b ∣ |a|-|b| ab
S 2 = ∑ i = 0 b C ( b , i ) ∑ j = 1 a − b − 1 C ( a , i + j ) S_2=\sum_{i=0}^bC(b,i)\sum_{j=1}^{a-b-1}C(a,i+j) S2=i=0bC(b,i)j=1ab1C(a,i+j) ∑ j = 1 a − b − 1 ∑ i = 0 b C ( b , b − i ) C ( a , i + j ) \sum_{j=1}^{a-b-1}\sum_{i=0}^bC(b,b-i)C(a,i+j) j=1ab1i=0bC(b,bi)C(a,i+j) ∑ j = 1 a − b − 1 C ( a + b , b + j ) \sum_{j=1}^{a-b-1}C(a+b,b+j) j=1ab1C(a+b,b+j)然后用算出上式这道题就结束了,模数其实是 1 e 9 1e9 1e9,非质数,考虑扩展卢卡斯, 1 0 9 = 2 9 5 9 10^9=2^95^9 109=2959,对两个质数次幂做卢卡斯然后 c r t \tt crt crt合并就行了,需要预处理对于质因子 2 2 2 5 5 5的阶乘,算卢卡斯就可以 O ( log ⁡ 2 ) O(\log^2) O(log2)了(需要快速幂)

还有问题,最后的除 2 2 2怎么解决, 2 2 2的次幂可以直接搞指数,对于我们算的组合数由于是杨辉三角上的 1 1 1行,具有其对称性,只用算一边就可以了,而如果 a + b a+b a+b是偶数 C ( a + b , ( a + b ) / 2 ) C(a+b,(a+b)/2) C(a+b,(a+b)/2)没有配对的,把他化到上一行变成 C ( a + b − 1 , ( a + b ) / 2 ) C(a+b-1,(a+b)/2) C(a+b1,(a+b)/2)就行了。还有一种方法就是把模数变成 2 e 9 2e9 2e9最后就可以直接除 2 2 2了。

#include <cstdio>
const int p = 1e9;
#define int long long
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,m,a,b,k,y[20],mod[2]={512,1953125},bs[2]={2,5},fac[2][2000000];
void exgcd(int a,int b,int &x,int &y)
{
	if(!b){x=1;y=0;return ;}
	exgcd(b,a%b,y,x);y-=x*(a/b);
}
int inv(int v,int p)
{
	int x,y;exgcd(v,p,x,y);
	return (x%p+p)%p;
}
int qkpow(int a,int b,int p)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%p;
		a=a*a%p;
		b>>=1;
	}
	return r;
}
int fuck(int n,int t)
{
	if(!n) return 1;
	return qkpow(fac[t][mod[t]-1],n/mod[t],mod[t])
	*fac[t][n%mod[t]]%mod[t]*fuck(n/bs[t],t)%mod[t];
}
int L(int n,int m,int t)
{
	int ind=0;
	for(int i=n;i;i/=bs[t]) ind+=i/bs[t];
	for(int i=m;i;i/=bs[t]) ind-=i/bs[t];
	for(int i=n-m;i;i/=bs[t]) ind-=i/bs[t];
	if(ind>=9) return 0;
	int x=fuck(n,t),y=fuck(m,t),z=fuck(n-m,t);
	return x*inv(y,mod[t])%mod[t]*inv(z,mod[t])%mod[t]*qkpow(bs[t],ind,mod[t])%mod[t];
}
void pr(int x)
{
	for(int i=1;i<=k;i++,x/=10) y[i]=x%10;
	for(int i=k;i>=1;i--) printf("%lld",y[i]);
	puts("");
}
signed main()
{
	fac[0][0]=fac[1][0]=1;
	for(int i=1;i<mod[0];i++)
		fac[0][i]=(i%2)?i*fac[0][i-1]%mod[0]:fac[0][i-1];
	for(int i=1;i<mod[1];i++)
		fac[1][i]=(i%5)?i*fac[1][i-1]%mod[1]:fac[1][i-1];
	while(~scanf("%lld %lld %lld",&a,&b,&k))
	{
		int r2=0,r5=0;
		if(a==b)//求C(2a,a) 
		{
			r2=L(2*a-1,a,0);r5=L(2*a-1,a,1);
			r2=r2*mod[1]%p*inv(mod[1],mod[0])%p;
			r2=(r2+r5*mod[0]%p*inv(mod[0],mod[1])%p)%p;
			pr((qkpow(2,a+b-1,p)-r2+p)%p);
		}
		else
		{
			if((a+b)%2)//对称,算一边 
			{
				for(int i=b+1;i<=(a+b)/2;i++) r2=(r2+L(a+b,i,0))%p;
				for(int i=b+1;i<=(a+b)/2;i++) r5=(r5+L(a+b,i,1))%p;
				r2=r2*mod[1]%p*inv(mod[1],mod[0])%p;
				r2=(r2+r5*mod[0]%p*inv(mod[0],mod[1])%p)%p;
				pr((qkpow(2,a+b-1,p)+r2)%p);
			}
			else//单独算C(a+b,(a+b)/2) 
			{
				for(int i=b+1;i<(a+b)/2;i++) r2=(r2+L(a+b,i,0))%p;r2=(r2+L(a+b-1,(a+b)/2,0))%p;
				for(int i=b+1;i<(a+b)/2;i++) r5=(r5+L(a+b,i,1))%p;r5=(r5+L(a+b-1,(a+b)/2,1))%p;
				r2=r2*mod[1]%p*inv(mod[1],mod[0])%p;
				r2=(r2+r5*mod[0]%p*inv(mod[0],mod[1])%p)%p;
				pr((qkpow(2,a+b-1,p)+r2)%p);
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值