2016暑期集训2——数论(知识点,模板,简单题题解)
知识点
模运算。除法取模可以利用逆元。比如a对模m的逆是b,那么(k/a)modm=(k mod m)*(b mod m) mod m。以及快速幂。
区间素数筛。要筛a到b,先打一个1到根号b的表,在往上筛。
扩展欧几里得。扩展欧几里德算法是用来在已知a, b时求解一组x,y,使它们满足:ax+by = gcd(a, b) ,且abs(x)+abs(y)最小。可以利用它求逆。
容斥原理!!!很不好想!!!HDU 4135 和下面一道题CodeForces 630K。
比如求1到某范围内与2.3.5互质的数个数,可以先求不与他们互质的数,就是求2的约数个数+3的约数个数+5的约数个数-6的约数个数-10的约数个数-15的约数个数+30的约数个数。质因数分解。根号n的复杂度
欧拉函数。对正整数n,欧拉函数是少于或等于n的数中与n互质的数的数目。例如phi(8)=4,因为1,3,5,7均和8互质。Euler函数表达通式:phi(x)=x(1-1/p1)(1-1/p2)(1-1/p3)(1-1/p4)…(1-1/pn),其中p1,p2……pn为x的所有素因数,x是不为0的整数。phi(1)=1(唯一和1互质的数就是1本身)。可以打表,思路类似素数筛。
组合数计算,取模。
- 当n,m很小的时候(不需要取模即可表示),只需计算正常计算即可。只需注意计算时要防止乘爆,应采取先乘后除的方法,先乘(n-i),后除(1+i)。而且C(a,b)=C(a,a-b),可以让b=min(b,a-b)。
- 若n或m固定,则可以用组合数性质提前打表处理。
- 若组合数较大需要取模,则应采用计算公式C(n,m) = n!/m!/(n-m)!其中除法注意要用逆代替,其中逆可以先预处理,打好表,也可以先求(n-m)!%MOD,在取逆。
- Lucas定理。当a,b比较大,容易超时,这时可以用Lucas定理。
- 快速幂求逆。利用费马小定理,a^(n-2)= 1(mod n)
模板
- 快速幂
int q_pow ( int a , int b )
{
int res = 1;
while ( b > 0 )
{
if ( b % 2 )
{
res = res*a;
}
a = a*a;
b = b / 2;
}
return res;
}
- 组合数
ll C ( ll a , ll b )
{
ll res = 1;
for ( ll i = 1; i <= b; i++ )
{
res *= ( a - i + 1 );
res /= ( i );
}
return res;
- 组合数取模
ll extend_gcd ( ll a , ll b , ll &x , ll &y )
{
if ( b == 0 )
{
x = 1;
y = 0;
return a;
}
else
{
ll r = extend_gcd ( b , a%b , y , x );
y -= x*( a / b );
return r;
}
}
ll mod_inverse ( ll a , ll m )
{
ll x , y;
ll d = extend_gcd ( a , m , x , y );
if ( d == 1 )
{
return ( x%m + m ) % m;
}
return -1;
}
ll CMOD ( ll a , ll b ,ll c)
{
//b = min ( b , a - b );
ll res = 1;
ll ans = 1;
for ( ll i = 1; i <= b; i++ )
{
res = res*( a - i + 1 ) % c;
ans = ans*i%c;
}
return res*mod_inverse ( ans , c ) % c;
}
- 素数筛
for (int i = 2; i < MAXN; i++)
{
if (isprime[i]==0)
{
for (j = 2; i*j < MAXN; j++)
{
isprime[i*j] = 1;
}
vec.push_back(i);
}
}
- 欧几里得,拓展欧几里得,利用拓展欧几里得求逆。
int gcd(int a,int b)
{
return b==0 ? a : gcd(b,a%b);
}
int extend_gcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x = 1;
y = 0;
return a;
}
else
{
int r = extend_gcd(b,a%b,y,x);
y -= x*(a/b);
return r;
}
}
int mod_inverse(int a,int m)
{
int x,y;
int d = extend_gcd(a,m,x,y);
return (x%m+m)%m;
}
- 欧拉函数表。
//phi(x)=x*(1-1/p1)(1-1/p2)(1-1/p3)…(1-1/pn),其中p1,p2……pn为x的所有素因数
//大体思路就是每次用一个质数往上筛,把因子包含这个质数的数乘上(1-1/p),p是该质数
for(i=1;i<MAXN;i++)
{
phi[i] = i;//先赋为x
}
for(i=2;i<MAXN;i++)
{
if(phi[i]==i)//说明它是一个素数,因为前面处理时没处理到i,说明i不是某个数的倍数。
{
for(j=i;j<MAXN;j+=i)
{
phi[j] = phi[j]/i*(i-1);//乘
}
}
}
- 打阶乘、阶乘的逆取模的表,组合数取模
LL fact[MAXN];
LL inv[MAXN];
LL f[MAXN];
void pre()
{
//MOD是要模的数
//fact是阶乘取模表,inv是阶乘的逆取模
M(fact, 0);M(inv, 0);M(f, 0);
const LL MOD=mod;
fact[0]=1;
f[0]=1;
inv[0]=1;
fact[1]=1;
f[1]=1;
inv[1]=1;
for(int i=2;i<MAXN;i++)
{
fact[i]=(LL)(fact[i-1]*i)%MOD;
f[i]=(LL)(MOD-MOD/i)*f[MOD%i]%MOD;
inv[i]=(LL)inv[i-1]*f[i]%MOD;
}
}
LL CMOD(LL a, LL b)
{
if(b>a) return 0;
if(b==a) return 1;
LL res=0;
res=(((fact[a]*inv[b])%mod)*inv[a-b])%mod;
return res;
}
- Lucas定理。
long long Lucas(long long a,long long b)
{
if(b==0) return 1;
return C(a%MOD,b%MOD)*Lucas(a/MOD,b/MOD)%MOD;
}
简单题题解
C -POJ 2407 裸的欧拉函数
E -CodeForces 630F 小组合数
从n个人里面选5或6或7个。
裸的小组合数,直接输出C(n,5)+C(n,6)+C(n,7)D -CodeForces 630B 裸的快速幂
求1.000000011的n次幂。B -CodeForces 577C Vasya and Petya’s Game(不是裸的。)
大意是A想一个1到n的数x,B知道n的情况下向A询问,x可以被y1整除么?可以被y2整除么…… B一气问完,A一起回答。问B最少问几次,每次的y是什么。
做法是唯一分解定理,把n分解,每次问一个质因子,如果质因子的幂次也在n之内,那么该幂次也要问。比如n是26,你要光问5是区分不出5和25的。using namespace std; const int MAXN = 1007; typedef long double ld; typedef long long ll; int q_pow ( int a , int b ) { int res = 1; while ( b > 0 ) { if ( b % 2 ) { res = res*a; } a = a*a; b = b / 2; } return res; } bool isprime [ MAXN ]; vector<int> prime_list; int main ( ) { int n; scanf ( "%d" , &n ); if ( n == 1 ) printf ( "0\n" ); else if ( n == 2 ) printf ( "1\n2\n" ); //else if ( n == 3 ) printf ( "2\n2 3\n" ); else { prime_list.clear ( ); memset ( isprime , 0 , sizeof ( isprime ) ); for ( int i = 2; i <= n; i++ ) { if ( isprime [ i ] == 0 ) { for ( int j = 2; i*j <= n; j++ ) { isprime [ i*j ] = 1; } prime_list.push_back ( i ); } } int k = 0; queue<int> res; for ( int i = 0; i < prime_list.size ( ); i++ ) { for ( int j = 1; ; j++ ) { int t = q_pow ( prime_list [ i ] , j ); if ( t <= n ) { res.push ( t ); } else break; } } printf ( "%d\n" , res.size ( ) ); for ( int i = 0; !res.empty ( ); i++ ) { if ( i == 0 ) { int temp = res.front ( ); printf ( "%d" , temp ); res.pop ( ); } else { int temp = res.front ( ); printf ( " %d" , temp ); res.pop ( ); } } printf ( "\n" ); } return 0; }
G -CodeForces 630K 容斥
见上面容斥的讲解。
int main ( ) { ll n; scanf ( "%lld" , &n ); int a [ 4 ] [ 10 ] = { { 2 , 3 , 5 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , } , { 6 , 10 , 14 , 15 , 21 , 35 , 0 , 0 , 0 , 0 } , { 30 , 42 , 70 , 105 , 0 , 0 , 0 , 0 , 0 , 0 } , { 210 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } }; ll res = n; for ( int i = 0; i < 4; i++ ) { for ( int j = 0; j < 10; j++ ) { if ( a [ i ] [ j ] != 0 ) { if ( i % 2 == 0 ) { res -= ( n / a [ i ] [ j ] ); } else { res += ( n / a [ i ] [ j ] ); } } } } printf ( "%lld\n" , res ); return 0;
F -CodeForces 630G
大意:5个A旗,3个B旗,放到n个桌子上。有多少种放法。直接组合。
#include<cstdio> using namespace std; typedef long long ll; ll C ( ll a , ll b ) { b = a - b; ll res = 1; for ( ll i = 1; i <= b; i++ ) { res *= ( a - i + 1 ); res /= ( i ); } return res; } int main ( ) { ll n; scanf ( "%lld" , &n ); ll res=0; res = C ( 4 + n , n - 1 )*C ( 2 + n , n - 1 ); printf ( "%lld" , res ); return 0; }
H -数据有问题而且水题。