(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 排队
插空法
直接算不好算,我们可以考虑老师不相邻=不考虑老师相邻-老师相邻
因为男生能相邻,所以把男生当背景板,把女生插空
不考虑老师相邻->把老师也扔进背景板里(大雾:
考虑老师相邻->俩老师捆一起,相当于一个男生:
化简:
得:
注意到答案非常大会爆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;
}