一些数学知识补充
1 分数取模
需要对一个分数 qp取模时,若 gcd(q,m)=1
则 p∗q−1≡p×qm−2(modm)
第二场A题,答案需令 n−11 对 109+7 取模
故代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
long long qpow(long long a,long long b){
long long res=1;
while(b){
if(b & 1){
res=res*a%mod;
}
a=a*a%mod;
b>>=1;
}
return res;
}
int t;
int main(){
cin>>t;
long long ans=1;
while(t--){
long long n,m;
cin>>n>>m;
if(n==1)
ans*=1;
else if(m==0)
ans=0;
else{
ans=ans*qpow(n-1,mod-2)%mod;
}
cout<<ans<<endl;
}
return 0;
}
2 数论知识补充
ab互为乘法逆元
ab≡1(modf)
由于扩展欧几里得可解方程 ax+by=1
故可将原式变形 ax+fy=1,x为a的乘法逆元
费马小定理(求分数逆元)
如果p为质数,则 ap−a是p的倍数
也可写作 ap−1≡1(modp)
扩展欧几里得
一层层写出辗转相除法的表达式,外层的式子不断代入内层,最终可得gcd的用a和b的线性表示
ax1+by1=bx2+(amodb)y2
ax1+by1=bx2+(a−(a/b)∗b)y2=ay2+bx2−(a/b)∗by2
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{ //1的情况
x=1;
y=0;
return a;
}
int r=exgcd(b, a%b, x, y);
int t=y;
y=x-(a/b)*y; //2的情况
x=t;
return r;
}
3 思维+动规(形式上)
题目链接
大概就是,对于一个由A,B组成的字符串,要求能拆分成n个AB子串和m个BA子串。给定n和m,求出合法字符串的种数。
对于一个合法串,我们可以令前n个A构成AB子串,前m个B构成BA子串;反过来,能够满足这两个条件的也是合法串
用前缀思路,无法直接求某一长度字符串的种类数,可不断更新求出某一(n,m)下的合法的前缀数。令dp[i][j]表示i个A,j个B时合法前缀的种类数
前缀的合法性为:譬如向一个前缀尾部添加A,如果此时i<n,自然可行,因为AB中的A要尽可能早地出场;而i>=n时,再添加A只能作为BA中的A,此时如果前面有足够的B来搭配,也可行,即i<n+j,其中有贪心的思想,我们认为此时可以认为之前出现的B均作为BA中的B;若不是,则说明B的数量已经超过BA的需求量,即已经开始为AB提供B元素了。此时其实可以将剩余的A和B以任意顺序附着在后面。
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int dp[2001][2001];
int n,m;
int main(){
while(cin>>n>>m){
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=0;i<=n+m;i++){
for(int j=0;j<=n+m;j++){
if(i<=n+j&&i>0) dp[i][j]=(dp[i-1][j]+dp[i][j])%mod;
if(j<=m+i&&j>0) dp[i][j]=(dp[i][j-1]+dp[i][j])%mod;
}
}
cout<<dp[n+m][n+m]<<endl;
}
return 0;
}
另:超时几次结果发现是每次memset整个数组太耗时,有功夫研究一下它和遍历初始化方法的速度差异。