题目
例如
2
0 0
2 1
------------
1
0
分析和思路
每一行的头和尾是1,每一行的其他位置,如果正上方和左上角中只有一个1,则为1,否则为0
再仔细看这句话:如果 ( n 一 1 , i ) (n 一1,i) (n一1,i)和 ( n ― 1 , i − 1 ) (n ―1,i-1) (n―1,i−1)之中只有一个1,那么 ( n , i ) (n,i) (n,i)是1,否则是0
可以理解为 ( n , i ) = [ ( n − 1 , i ) + ( n − 1 , i − 1 ) ] % 2 (n,i)=[(n-1,i)+(n-1,i-1)]\%2 (n,i)=[(n−1,i)+(n−1,i−1)]%2
我们都知道组合数有一个公式: ( n + 1 k ) = ( n k ) + ( n k − 1 ) {n+1 \choose k}={n \choose k}+{n \choose k-1} (kn+1)=(kn)+(k−1n) C n + 1 k = C n k + C n k − 1 C_{n+1}^{k}=C_{n}^{k}+C_{n}^{k-1} Cn+1k=Cnk+Cnk−1
如果看成组合数,那么就有 [ ( n − 1 , i ) + ( n − 1 , i − 1 ) ] % 2 = ( n , i ) % 2 [(n-1,i)+(n-1,i-1)]\%2=(n,i)\%2 [(n−1,i)+(n−1,i−1)]%2=(n,i)%2
因此转化为一个组合数取模的问题
利用高中教过的组合数公式 C n m = n ! m ! × ( n − m ) ! C_{n}^{m}=\frac{n!}{m!\times (n-m)!} Cnm=m!×(n−m)!n!,想偷懒用python的math库写
import math
def main():
t = int(input())
while t:
t = t - 1
n, m = map(int, input().split())
if m > n and m != 0:
ans = 0
elif m == 0:
ans = 1
else:
ans = math.factorial(n) // (math.factorial(m) * math.factorial(n - m)) % 2
print(ans)
pass
if __name__ == '__main__':
main()
放到平台上运行,毫不意外地超时了,也是,绝对没有那么简单
经过一番查询,发现由于这里的模2是一个很小的质数,且很可能问题规模很大,所以不能通过简单的递推公式求解。下面介绍一下大名鼎鼎的卢卡斯定理
上图来自卢卡斯定理 - OI Wiki (oi-wiki.org)
实现:前一项递归调用,而后面一项算出组合数
// C++ Version
long long Lucas(long long n, long long m, long long p) {
if (m == 0) return 1;
return (Lucas(n / p, m / p, p) * C(n % p, m % p, p)) % p;
}
# Python Version
def Lucas(n, m, p):
if m == 0:
return 1
return (Lucas(n // p, m // p, p) * C(n % p, m % p, p)) % p
接下来的重点是计算组合数,由于自己学识尚浅,只知道杨辉三角的递推公式 C n m = C n − 1 m + C n − 1 m − 1 C_{n}^{m}=C_{n-1}^{m}+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1
(因为很好记,从n个数中选m个数,只有两种情况:①第n个数不选,从 n − 1 n-1 n−1个数中选m个数,即 C n − 1 m C_{n-1}^{m} Cn−1m;②第n个数选,则相当于从 n − 1 n-1 n−1个数中选 m − 1 m-1 m−1个数,即 C n − 1 m − 1 C_{n-1}^{m-1} Cn−1m−1。那么总方案数即为两者之和)
但是后来发现这个方法就算用滚动数组节省空间,其时间复杂度 O ( n 2 ) O(n^2) O(n2),还是很容易超时
最终参考了大佬的的方法:费马小定理求乘法逆元 + 快速幂求解组合数
计算组合数的几种方法总结_liuzibujian的博客_(C++)
Python快速求组合数C(n,m)三种方法整理_果冻er的博客(python)
代码实现
C++
#include<bits/stdc++.h>
//typedef long long ll;
//这里尝试了一下int不会爆,如果发现问题规模过大,int无法承载爆了就需要改成long long
using namespace std;
int p = 2;//素数模为2
int quickpow(int a,int b)//快速幂,求a的b次方
{
int res = 1;
while(b)
{
if(b%2!=0)res = res*a % p;
a=a*a % p;
b >>= 1; //右移一位
}
return res;
}
int C(int n,int m)
{
if(m>n)return 0;//若y>x输出0
int a=1,b=1;
for(int i=n-m+1;i<=n;++i)
a=a*i%p;
for(int i=2;i<=m;++i)
b=b*i%p;
return a*quickpow(b,p-2)%p;
}
int Lucas(int n,int m)//求C(n,m)%2
{
if(!m)return 1;//y=0,第0列都是1
else return (Lucas(n/p,m/p) * C(n%p,m%p))%p;//递归
}
int main()
{
int x,y;
int t;
cin>>t;//测试样例组数
while(t--)
{
cin>>x>>y;//坐标
cout<<Lucas(x,y)<<endl;//坐标对应元素(1/0)
}
return 0;
}
python
python的组合数计算我调用了math里求阶乘的方法factorial()
import math
def power(x,y): #求x的y次方
p = 2
res = 1
while y:
if y % 2 != 0:
res *= (x % p)
y >>= 1
x *= (x % p)
return res
def C(n,m,p):
if m > n:
return 0
a = (math.factorial(n)) % p
b = (power(math.factorial(m), (p - 2))) % p
c = (power(math.factorial(n - m), (p - 2))) % p
return a*b*c % p
# Python Version
def Lucas(n, m, p):
if m == 0:
return 1
return (Lucas(n // p, m // p, p) * C(n % p, m % p, p)) % p
def main():
p = 2
t = int(input())
while t:
t = t - 1
n, m = map(int, input().split())
print(Lucas(n, m, 2))
pass
if __name__ == '__main__':
main()
python的代码在平台上的运行时间比C++相比多了8倍,区别就在函数C的求解方法不同(原理相同都是费马小定理求乘法逆元)
中间超时的是最开始那个用高中组合数公式写的,严重超时
拓展
分享一篇很不错的文章:
⭐感谢您能看到这里,我会好好努力继续分享好的文章的!⭐