WHU 校赛2019 D题Dandelion的补题以及相关的知识点整理

上个周日在外面玩了一圈过后,去参加了武大的网络赛,当时我在负责D题的时候出了一点问题,当时以为只是一个简单的dp,后来发现条件没有加,然后加了过后测试了几组数据发现结果是对的,但是我一看当时给的数据范围...这根本就不可以用dp来,要么超时,要么就是内存空间不够大,直接爆内存,当时为了开辟内存想了各种各样的方法,结果最后没有搞出来,记得数据每个好像给的是一百万,然后想看看这个题的数据水不水,结果降低空间了以后,在第二点就卡住了,直接卡的超时了,然后就完全不想写了,自己对于组合数学这里掌握的不是很熟。

又因为这个星期的事情比较多,自己在忙着先把C++自己的专业课弄熟,然后最近一直在忙着钻研关于C++的事情,然后acm稍微有些放到了后面....想起来惭愧,但是空余时间一直在零零散散的看这个题目的知识点,现在终于到了周末,可以有时间集中起来处理一下了,在这个地方做一个整理吧,(菜的安详)咕咕咕咕咕咕咕。。。。。

分割线----------------------------------------------------------------------------------------------------------------------------------------------------------------

后来在整理这个题目的时候发现的知识点:

(其实没有这么多,这个题目主要就是 简单组合数学 用 费马小定理 求 逆元,然后直接套公式。
ans = C(n+m-1,m) - C(n+m-1,m-1),但是自己在整理知识点的时候又看到了其他的知识点就一起放在了这里)

1.费马小定理

2.欧拉定理(其实费马小定理就是欧拉定理的特殊情况啦)

3.同余方程组

4.中国剩余定理

5.逆元(1.费马小定理2.扩展欧几里得3.线性递推 然后还有一个什么欧拉表的什么的....这个不是很清楚,现在钻研到了这里了...)

6.组合数学

好像大概就是这些知识点,然后这一次主要讲这个题目的知识点吧,然后可能下几篇在分开来讲其他的知识点,毕竟要写的东西有点多,自己以后复习也不好复习

分割线----------------------------------------------------------------------------------------------------------------------------------------------------------------

大概题意:

就是大概有一个网格,然后从最左上角(0,0)开始,到题目给出的一个点,问有多少种方法,要求横坐标在整个过程中必须小于纵坐标,数值可能有点大,然后对结果进行对1e9+7取余处理,然后点的横纵坐标的给的范围是在横竖1e6的范围内

首先给出代码,

#include <bits/stdc++.h>
using namespace std;
const int maxn = 500005, mod = 1e9 + 7;
int mi[maxn];
inline int quick(int a, int b) {
	int res = a, ans = 1;
	for (int i = 0; i < 31; i ++) {
		if (1 << i & b) ans = (long long)ans * res % mod;
		res = (long long)res * res % mod;
	}
	return ans;
}
int C(int n, int m) {
	int tmp = (long long)mi[n - m] * mi[m] % mod;
	int ans = (long long)mi[n] * quick(tmp, mod - 2) % mod;
	return ans;
}
int main() {
	int n, m;
	mi[0] = 1;
	for (int i = 1; i <= 200000; i ++) mi[i] = (long long)mi[i - 1] * i % mod;
	int t;
	scanf("%d",&t);
	while (t--) {
		scanf("%d%d", &m, &n);
		printf("%d\n", (C(n + m - 1, m) - C(n + m - 1, m - 1) + mod) % mod);
	}
	return 0;
}

然后这个标程不是我的,是whu大佬们的,我截取一下dalao的当个模板讲一下吧:

首先是这个题目的中心公式ans = C(n+m-1,m) - C(n+m-1,m-1)是怎么推出来的,因为对于像这种网格里面从一个点到另外一个点要走多少步,由我们高中的知识就可以知道,这是组合数学的问题,比如到(m,n)就应该是C(m+n,m)/C(m+n,n),但是后来加上了限制条件,也就是说,首先把所有可以的情况全部都列举出来,然后减去不合理的就可以了,

因为题目中要求横坐标必须要小于纵坐标,所以应该是从点(0,1)开始的,所以一共走了(n+m-1)步,所以从这么多步里面来进行选择,因为往横坐标的步数一步都没有减少,所以从(n+m-1)步里面选择出横坐标方向的m步,所以最开始的是

C(n+m-1,m),然后我们来分析后来不符合条件的状况,是从(1,0)开始的,为什么从这里开始呢,因为给出的越界条件是

x<y,所以最边界的极限应该是y=x,我们发现从(1,0)开始的话,正好是取y=x这条线的轴对称路线,也正好是越界的条件,所以我们就可以得出首先也是(n+m-1)步,横坐标少了一步,所以选m-1步,所以越界的总数为C(n+m-1,m-1),将它减去就可以了,为了防止最后写出来的是一个负数的结果,我们可以加上一个mod再取余即可。

以上就是核心公式的思路,证毕。

分割线----------------------------------------------------------------------------------------------------------------------------------------------------------------

然而有了核心公式,我们对于像组合数学这样的求解也不是很好求,我们再查阅了一定的资料后发现,对于像阶乘除以阶乘这样的大数据之间相处,肯定会爆精度,而且,根据同余方程组,我们可以知道

(a+b)%p=(a%p+b%p)%p; 同理- 和* 也是可以的,但是/不可以,我们可以考虑将这样的除法改成乘法就行了,这就涉及到了逆元,

首先介绍费马小定理,假如p是素数,且(a,p)=1,那么a^(p-1)≡1(mod p) ①

然后是逆元,设x为a对p取模的逆元,那么式子就可以表示为ax≡1(mod p) ② ,根据费马小定理,我们可以知道

①式进行分解可以得到 a*a^(p-2)≡1(mod p),所以此时逆元应该为a^(p-2),这个结果的求出我们可以用long long 范围的快速幂来进行求解即可,实际上这也是求逆元的第一种方法,即使用费马小定理求解:

具体的代码板子如下:利用快速幂求解,复杂度为O(logn)

long long poww(long long a,long long b,long long mod)
{
	long long res=1,ans=a;
	while(b!=0)
	{
		if(b&1)
		res=(res*ans)%mod;
		ans=(ans*ans)%mod;
		b>>=1;
		
	}
	return res;
} 
long long inverse(long long a,long long mod)
{
	return poww(a,mod-2,mod);
}

求这个逆元的主要作用主要是再进行这样的除法时可以进行相应的转换,

给一个定义: (a/b)%p=(a*inv(b))%p=((a%p)*(inv(b)%p))%p; 

然后就是这个题关于C(m,n)的处理,因为C(m,n)等于,m!/n!(m-n)!;所以我们在题目最开始的时候

for (int i = 1; i <= 200000; i ++) mi[i] = (long long)mi[i - 1] * i % mod; 这样类似于前缀和的方式进行处理阶乘即可,然后先用中间变量保存n!(m-n)!,然后再用这个变量取逆元返回结果即可,这个就是这个题目的完整操作。

关于求逆元,其实还有一个方法,那就是用扩展欧几里得来求:

定理:对于任意的两个正整数(负整数将负号提至系数)a,b必然存在两个整数x,y使得ax+by=gcd(a,b)

扩展欧几里得求它时,满足了条件a,b互质,即gcd(a,b)为1.

在这里,我们先写下板子:下一篇再详细写解法

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
	if(b==0)
	{
		x=1;
		y=0;
		return a;
	}
	int gcd=exgcd(b,a%b,x,y);
	int tx=x,ty=y;
	x=ty;
	y=tx-(a/b)*ty;
	return gcd;
}
int main()
{
	int x,y,a,b;
	while(~scanf("%d%d",&a,&b))
	{
		cout<<"a和b的最大公约数为: "<<exgcd(a,b,x,y)<<endl;
		cout<<"ax+by=gcd(a,b) 的一组解是: " <<x<<" "<<y<<endl;
	}
	return 0;
}

特别的,当gcd(a,b)=1,时,求逆元的时候,x为所求逆元,为了避免负数,所以可以加上如下程序:

int ans(int a,int n)//ax=1(mod n)
{
	int d,x,y;
	d=exgcd(a,n,x,y);
	if(d==1)
	return (x%n+n)%n;
	else
	return -1;
}

好啦,这个题目就整理到这里啦~~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值