1.著名的欧几里得算法
附上一些gcd和lcm的简单性质:
lcm(S/a, S/b) = S/gcd(a, b)
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
2.扩展欧几里得算法
int ex_gcd(int a,int b,int& x,int& y)
{
if(b==0){
x = 1, y = 0;
return a;
}
int d = ex_gcd(b,a%b,y,x);//注意x,y互换位置
y -= a/b*x;
return d; //返回值是a,b的GCD
}
或者另一种实现
void ex_gcd(int a,int b,int &d,int &x,int &y)
{
if(b==0){
x = 1, y = 0, d = a;
}
else{
gcd(b,a%b,d,y,x);
y -= a/b*x;
}
}
3.快速幂
long long pow(long long a,long long b)
{
long long ans = 1,base = a;
while(b){
if(b&1)
ans *= base;
base *= base;
b>>=1;
}
return ans;
}
4.快速幂取模
long long pow_mod(long long a,long long b,long long mod)
{
a %= mod;//视情况而定,如果a*a可能爆long long,就加上
long long ans = 1,temp = a;
while(b){
if(b&1)
ans = (ans*temp) % mod;
temp = temp*temp%mod;
b /= 2;
}
return ans;
}
5.线性时间素数筛
const int maxn = 1e6+10;
bool vis[maxn];
int prime_cnt;
int prim[maxn/10+5];
void Prime()
{
prime_cnt = 0;
memset(vis,0,sizeof(vis));
for(int i=2;i<maxn;i++){
if(vis[i]==0)
prim[prime_cnt++] = i;
for(int j=0;j<prime_cnt && i*prim[i]<maxn;j++){
vis[i*prim[i]] = 1;
if(i % prim[i] == 0)
break;
}
}
}
6.区间素数筛
const int maxn = 1e5+10;
bool vis[maxn];
int prim[maxn];
int cnt;
void Prime(long long L,long long R)
{
long long len = R-L+1;
memset(vis,0,sizeof(vis));
int flag = 0;
if(L%2==1)
flag++;
for(int i=flag;i<len;i+=2)
vis[i] = 1;
int m = sqrt(R+0.5);
for(int i=3;i<=m;i+=2){
if(i>L && vis[i-L] == true)
continue;
int j= (L/i)*i;
if(j<L)
j+=i;
if(j==i)
j+=i;
j-=L;
for(;j<len;j+=i)
vis[j] = 1;
}
if(L==1)
vis[0] = true;
if(L<=2)
vis[2-L] = false;
cnt = 0;
for(int i=0;i<len;i++)
if(!vis[i])
prim[cnt++] = i+L;
}
7.欧拉函数
//直接求法,复杂度O(sqrt(n))
int phi(int n)
{
int ans = n,a = n;
for(int i=2;i*i<a;i++){
while(a%i==0){
ans = ans/i*(i-1);//先进行除法防止中间数据溢出
while(a%i==0)
a /= i;
}
}
if(a>1)
ans = ans/a*(a-1);
return ans;
}
求区间内所有值的欧拉函数值,可以使用筛法
//欧拉筛
int euler[maxn];
void Euler()
{
for(int i=2;i<maxn;i++)
euler[i] = i;
for(int i = 2;i<maxn;i++)
if(euler[i] == i)
for(int j=i;j<maxn;j+=i)
euler[j] = euler[j]/i*(i-1);
}
8.逆元
扩展欧几里得求解逆元
int ex_gcd(int a,int b,int& x,int& y)
{
if(b==0){
x = 1,y = 0;
return a;
}
int d = ex_gcd(b,b%a,y,x);
y -= a/b*x;
return d;
}
int inv(int a,int p)//不存在则返回-1
{
int d,x,y;
d = ex_gcd(a,p,x,y);
return d==1?(x%p+p)%p:-1;
}
费马小定理求解逆元
int inv(int a,int p)
{
return pow_mod(a,p-2,p);//或许会用到降幂公式
}
若模数p为素数,可根据逆元的性质求解:inv(a) = (p - p / a) * inv(p % a) % p
//求a关于p的逆元,注意:a要小于p,最好传参前先把a%p一下
int inv(int a,int p)
{
return a==1 ? 1 : (p-p/a)*inv(p%a,p)%p;
}
运用刚刚提到的性质,可以在O(n)的时间内求出1~n的逆元
const long long n = 1e5+10;
const long long Mod = 1e9+7;
long long inv[n];
void Inv()
{
inv[1] = 1;
for(int i=2;i<n;i++)
inv[i] = (Mod-Mod/i)*inv[Mod%i]%Mod;
}
9.中国剩余定理
模数互素时
long long CRT(int n,long long a[],long long m[])
{
long long M = 1,ans = 0;
for(int i=0;i<n;i++)
M *= m[i];
for(int i=0;i<n;i++){
long long temp = M/m[i];
ans = (ans+temp*inv(temp,m[i])*a[i])%M;
}
return (ans+M)%M;
}
模数不互素时
typedef long long ll;
typedef pair<ll, ll> pll;
//求解A[i]x = B[i] (mod M[i]),总共n个线性方程组
pll CRT(int n, ll A[], ll B[], ll M[]) {
ll ans = 0, m = 1;
for(int i = 0; i < n; i ++) {
ll a = A[i] * m, b = B[i] - A[i]*ans, d = gcd(M[i], a);
if(b % d != 0)
return pll(0, -1);//答案不存在,返回-1
ll t = b/d * inv(a/d, M[i]/d)%(M[i]/d);
ans = ans + m*t;
m *= M[i]/d;
}
ans = (ans % m + m ) % m;
return pll(ans, m);//返回的x就是答案,m是最后的lcm值
}
10.组合数取模
杨辉三角求解,复杂度O(
n2
),可以用来求解1-1000范围内的组合数
const int N = 1000 + 5;
const int MOD = 1e9 + 7;
int comb[N][N]; //comb[n][m]就是C(n,m)
void init(){
for(int i = 0; i < N; i ++){
comb[i][0] = comb[i][i] = 1;
for(int j = 1; j < i; j ++){
comb[i][j] = comb[i-1][j] + comb[i-1][j-1];
comb[i][j] %= MOD;
}
}
}
逆元求解,复杂度O(n)
const int N = 200000 + 5;
const int MOD = (int)1e9 + 7;
int F[N], Finv[N], inv[N];//F是阶乘,Finv是逆元的阶乘
void init(){
inv[1] = 1;
for(int i = 2; i < N; i ++){
inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
}
F[0] = Finv[0] = 1;
for(int i = 1; i < N; i ++){
F[i] = F[i-1] * 1ll * i % MOD;
Finv[i] = Finv[i-1] * 1ll * inv[i] % MOD;
}
}
int comb(int n, int m){//comb(n, m)就是C(n, m)
if(m < 0 || m > n) return 0;
return F[n] * 1ll * Finv[n - m] % MOD * Finv[m] % MOD;
}
大组合数取模,卢卡斯定理,用于n和m特别大而p<1e5的情况
C(n, m) % p = C(n / p, m / p) * C(n%p, m%p) % p
11.康托展开
//康托展开,把一个数字num展开成一个数组s,k是数组长度
void cantor(int s[], LL num, int k){
int t;
bool h[k];//0到k-1,表示是否出现过
memset(h, 0, sizeof(h));
for(int i = 0; i < k; i ++){
t = num / fac[k-i-1];
num = num % fac[k-i-1];
for(int j = 0, pos = 0; ; j ++, pos ++){
if(h[pos]) j --;
if(j == t){
h[pos] = true;
s[i] = pos + 1;
break;
}
}
}
}
以及,康托逆展开
//康托逆展开,把一个数组s换算成一个数字num
void inv_cantor(int s[], LL &num, int k){
int cnt;
num = 0;
for(int i = 0; i < k; i ++){
cnt = 0;
for(int j = i + 1; j < k; j ++){
if(s[i] > s[j]) cnt ++;//判断几个数小于它
}
num += fac[k-i-1] * cnt;
}
}
12.容斥原理
容斥原理有很多实现方式,可以选择DFS,队列数组以及位操作的方式来实现。
以HDU-4135为例分别贴上代码
//队列数组
#include<cstdio>
using namespace std;
typedef long long ll;
int cnt,Cnt;
int N[100],que[100000];
void work(ll n)
{
//预处理n的所有质因子
Cnt = 0;
for(int i=2;i*i<n;i++)
if(n%i==0){
N[Cnt++] = i;
while(n%i==0)
n/=i;
}
if(n>1)
N[Cnt++] = n;
//这里是关键,在队列数组的顺位置决定了正负
cnt = 0;
int l;
for(int i=0;i<Cnt;i++){
l = cnt;
for(int j=0;j<l;j++)
que[cnt++] = que[j]*N[i];
que[cnt++] = N[i];
}
}
ll solve(ll a)
{
ll ans = a,flag = -1;
for(int i=0;i<cnt;i++){
ans += flag* a/que[i];
flag = - flag;
}
return ans;
}
int main()
{
int T;
scanf("%d",&T);
ll a,b,n;
for(int tt=1;tt<=T;tt++){
scanf("%I64d %I64d %I64d",&a,&b,&n);
work(n);
printf("Case #%d: %I64d\n",tt,solve(b)-solve(a-1));
}
}
//位操作
#include<cstdio>
using namespace std;
typedef long long LL;
int Cnt;
LL N[100];
void init(LL x){
Cnt = 0;
for(int i=2;i*i<x;i++)
if(x%i==0){
N[Cnt++] = i;
while(x%i==0)
x/=i;
}
if(x>1)
N[Cnt++] = x;
}
LL work(LL x){
//接下来容斥
LL ans = x, cnt, temp;
for(int i = 1; i < (1 << Cnt); i ++){
cnt = 0;
temp = 1;
for(int j = 0; j < Cnt; j ++){
if(i & (1 << j)){
temp *= N[j];
cnt ++;
}
}
if(cnt & 1) ans -= x / temp;
else ans += x / temp;
}
return ans;
}
int main(){
int T;
LL l, r, n;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas ++){
scanf("%I64d%I64d%I64d", &l, &r, &n);
init(n);
printf("Case #%d: %I64d\n", cas, work(r) - work(l-1));
}
}