2021.05.10讲题

(2021.05.10) 基础数学和组合数学2 (B G H by DReamLion)
upd:排队化简过程

B P2822 组合数问题

给出组合数公式,求满足0<=i<=n,0<=j<=min(i,m)的条件下,i选j的组合数是k的倍数有多少种可能
一看数据范围,n<=2000,m<=2000,t<=1e4,直接套公式是必然不可能过的
因为是多组数据,所以先看能不能预处理一下,然后每组只查询就可以了
怎么预处理?我们可以用杨辉三角来做,因为由杨辉三角的性质可知,第n行m个数可表示为C(n-1,m-1),正好可以用来求组合数,算杨辉三角的时候顺便把k模掉就行了
还需要用到二维前缀和,用f[][]数组来存前缀和:

f[i][j]=(f[i-1][j]+f[i][j-1]-f[i-1][j-1]);

注意输出的一个小细节:

if(n<m) printf("%lld
",f[n][n]);//n选m,但是m比n还大,所以当成n选n就行 

因为题目没说m一定大于n,所以如果不加这句会被卡掉40pts

#include<iostream>
#include<cstdio>
#include<cstdlib>
#define maxn 2010
#define ll long long
using namespace std;
template<typename T>
inline void read(T &x){
	x=0; bool flag=0; char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

ll t,k,n,m;
ll s[maxn][maxn],f[maxn][maxn];

void yanghui(){
	s[0][0]=s[1][0]=s[1][1]=1;//杨辉三角的前两行 
	for(int i=2;i<=2000;i++){
		s[i][0]=1;
		for(int j=1;j<=i;j++){
			s[i][j]=(s[i-1][j-1]%k+s[i-1][j]%k)%k;
			f[i][j]=(f[i-1][j]+f[i][j-1]-f[i-1][j-1]);//前缀和,上加左减左上加自己 
			if(s[i][j]==0) f[i][j]++;//满足结论,前缀和计数+1 
		}
		f[i][i+1]=f[i][i];//继承 ,便于调用 
	} 
}

int main(){
	read(t),read(k);
	yanghui();
	for(int i=1;i<=t;i++){
		read(n),read(m);
		if(n==0){printf("1
"); continue;}
		if(n<m) printf("%lld
",f[n][n]);//n选m,但是m比n还大,所以当成n选n就行 
		else printf("%lld
",f[n][m]);
	}
	return 0;
}

G P3223 排队

插空法
直接算不好算,我们可以考虑老师不相邻=不考虑老师相邻-老师相邻
因为男生能相邻,所以把男生当背景板,把女生插空
不考虑老师相邻->把老师也扔进背景板里(大雾:

[A_{n+2}^{n+2}*A_{m}^{m}*C_{n+3}^{m} ]

考虑老师相邻->俩老师捆一起,相当于一个男生:

[A_{2}^{2}*A_{n+1}^{n+1}*A_{m}^{m}*C_{n+2}^{m} ]

化简:

[frac{(n+2)!m!(n+3)!}{(n+3-m)!m!}- frac{2(n+1)!m!(n+2)!}{(n+2-m)!m!} ]

[frac{(n+2)(n+1)!(n+3)(n+2)!}{(n+3-m)(n+2-m)!}- frac{2(n+1)!(n+2)!}{(n+2-m)!} ]

[frac{(n+1)!(n+2)!}{(n+2-m)!}(frac{(n+2)(n+3)}{(n+3-m)}-2) ]

[frac{(n+1)!(n+2)!}{(n+2-m)!}frac{n^2+3n+2m}{n+3-m} ]

得:

[prod_{i=n+4-m}^{n+2} i*(n+1)!*(n^2+3*n+2*m) ]

注意到答案非常大会爆long long(long long只有10pts),我们必须写高精

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<vector>
#define ll long long
using namespace std;
template<typename T>
inline void read(T &x){
    x=0;bool flag=0;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
    for(;isdigit(c);c=getchar()) x=x*10+(c^48);
    if(flag) x=-x;
} 

ll n,m,tmp;
vector<int> ans;

vector<int> mul(vector<int> &A,int b){
	vector<int> C;
	int t=0;
	for(int i=0;i<A.size()||t;i++){
		if(i<A.size())t+=A[i]*b;
		
		C.push_back(t%10);
		t/=10;
	}
	while(C.size()>1&&C.back()==0) C.pop_back();
	return C;
}

int main(){
	read(n),read(m);
	tmp=n*n+2*m+3*n;
	ans.push_back(1);
	for(int i=1;i<=n+1;i++) ans=mul(ans,i);
	for(int i=n-m+4;i<=n+2;i++) ans=mul(ans,i);
	ans=mul(ans,tmp);
	int len=ans.size()-1;
	for(;len>=0;len--)printf("%d",ans[len]);
	printf("
");
	return 0;
}

H P3166 数三角形

和上一题类似,可以考虑总数-三点共线的情况
三点共线一共三种可能:在同一行,在同一列,在同一斜线上
注意给的是网格的n,m,格点数要++(下面就默认已经n++,m++了;代码里没在开始加,写的麻烦了)
因为要选三个格点,所以n选m就相当于n*m选3,这是总数
先减在同一行和在同一列的情况,因为这两种好算:

if(m>=2) ans-=(n+1)*mul(m+1);
if(n>=2) ans-=(m+1)*mul(n+1);

至于斜线上的情况,有个结论:
对于两点(a,b)(c,d),其中a>c,b>d,
在这两点的线段上有gcd(a-c,b-d)-1个整点
所以我们枚举线段两头的点,让第三个点在这条线段上,就能求了
为了优化复杂度,我们利用矩形的对称性,只枚举一个端点和第三个点,然后*2就得到了所有方案

#include<iostream>
#include<cstdio>
#include<cstdlib>
#define maxn 2010
#define ll long long
using namespace std;
template<typename T>
inline void read(T &x){
	x=0; bool flag=0; char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
	if(flag) x=-x;
}

ll m,n,ans;

ll mul(ll a){
	ll res;
	res=(a)*(a-1)*(a-2)/(1*2*3);
	return res;
}

int gcd(int a,int b){
	return (b==0)?a:gcd(b,a%b);
}

int main(){
	read(m),read(n);
//	m++;n++;
	ans=mul((m+1)*(n+1));
//	cout<<"*"<<ans<<endl;
	if(m>=2) ans-=(n+1)*mul(m+1);
	if(n>=2) ans-=(m+1)*mul(n+1);
//	cout<<"**"<<ans<<endl;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			ans-=2*(m-i+1)*(n-j+1)*(gcd(i,j)-1);
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/DReamLion/p/14752972.html