Tips
数学&计算几何
PI
const double PI=acos(-1);
圆内接多边形周长
sin函数的使用方法 2*r*sin(PI/n)
表示圆内接多边形的边长
组合数
ll C(ll n, ll m) {//组合数 C(n,m) if (m < 0) return 0; if (n < m) return 0; if (m > n - m) m = n - m; ll up = 1, down = 1; for (ll i = 0; i < m; i++) { up = up * (n - i) % mod; down = down * (i + 1) % mod; } return up * getInv(down) % mod; }
乘法逆元
这种方法只适用于对答案模一个大质数的情况。
乘法逆元:(a/b)%mod=a*(b^(mod-2)) mod为素数。
逆元可以利用扩展欧几里德或欧拉函数求得:
- 扩展欧几里德:bx+py=1 有解,x就是所求
- 费马小定理:b^(p-1)=1(mod p),故b*b^(p-2)=1(mod p),所以x=b^(p-2)
int inv(int a) { //return fpow(a, MOD-2, MOD); return a == 1 ? 1 : (long long)(MOD - MOD / a) * inv(MOD % a) % MOD; } LL C(LL n,LL m) { if(m < 0)return 0; if(n < m)return 0; if(m > n-m) m = n-m; LL up = 1, down = 1; for(LL i = 0 ; i < m ; i ++){ up = up * (n-i) % MOD; down = down * (i+1) % MOD; } return up * inv(down) % MOD; }
卢卡斯定理
当n和m比较大,mod是素数且比较小的时候(10^5左右),通过Lucas定理计算
Lucas定理:A、B是非负整数,p是质数。A B写成p进制:A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0]。
则组合数C(A,B)与C(a[n],b[n])C(a[n-1],b[n-1])…C(a[0],b[0]) mod p同余
即:Lucas(n,m,p)=C(n%p,m%p)Lucas(n/p,m/p,p)
#include<iostream> using namespace std; typedef long long ll; int quick_power_mod(int a,int b,int m){//pow(a,b)%m int result = 1; int base = a; while(b>0){ if(b & 1==1){ result = (result*base) % m; } base = (base*base) %m; b>>=1; } return result; } //计算组合数取模 ll comp(ll a, ll b, int p) {//composite num C(a,b)%p if(a < b) return 0; if(a == b) return 1; if(b > a - b) b = a - b; int ans = 1, ca = 1, cb = 1; for(ll i = 0; i < b; ++i) { ca = (ca * (a - i))%p; cb = (cb * (b - i))%p; } ans = (ca*quick_power_mod(cb, p - 2, p)) % p; return ans; } ll lucas(ll n, ll m, ll p) { ll ans = 1; while(n&&m&&ans) { ans = (ans*comp(n%p, m%p, p)) % p;//also can be recusive n /= p; m /= p; } return ans; } int main(){ ll m,n; while(cin>>n>>m){ cout<<lucas(n,m,10007)<<endl; } return 0; }
C(n % mod, m % mod) % mod;如果太大还是利用上面的逆元来处理。
半预处理
由于Lucas定理保证了阶乘的数均小于p,所以可以讲所有的阶乘先预处理,优化C(n,m)
mod的要求:p<10^6,且为素数
有效范围:1<=n,m<=10^9
//半预处理 const ll MAXN = 100000; ll fac[MAXN+100]; void init(int mod) { fac[0] = 1; for (int i=1; i<mod; i++){ fac[i] = fac[i-1] * i % mod; } } //半预处理逆元求C(n,m)%mod ll C(ll n, ll m) { if ( m>n ) return 0; return fac[n] * (GetInverse(fac[m]*fac[n-m], mod)) % mod; }
求一个数的因数
int work(int num) { int factor[1600], m = 0; for (int i = 1; i <= num / i; i++) { if (num % i == 0) { factor[++m] = i; if (i != num / i) factor[++m] = num / i; } } return m; }
分解质因数
const int maxn = 1005; ll prime_num[maxn], num; //质因数的个数 ll prime[maxn]; ll dp[maxn][maxn]; void divid(ll n){ //分解正整数n num = 0; if (n % 2 == 0) { ll term = 0; while (n % 2 == 0) { term++; n /= 2; } prime_num[num] = term; prime[num++] = 2; } for (ll i = 3; i * i <= n; i += 2) if (n % i == 0) { ll term = 0; while (n % i == 0) { term++; n /= i; } prime_num[num] = term; prime[num++] = i; } if (n > 1) { prime[num] = n; prime_num[num++] = 1; } }
苹果篮子
const int maxn = 1005; ll dp[maxn][maxn];//多少种放法 ll f(ll m, ll n){ // m个苹果 n个篮子 dp[0][0] = 1; for (int i = 1; i <= n; ++i) for (int j = 0; j <= m; ++j) if (j >= i) dp[i][j] = (dp[i][j - i] + dp[i - 1][j]) % mod; else dp[i][j] = dp[j][j]; return dp[n][m]; }
STL&编程
cin加速
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
二分
#include <algorithm> ll a[5] = {-1, 0, 1, 3, 4}; int main() { bool flag = binary_search(a, a + 5, 6); //有就返回1 没有就返回0 cout << lower_bound(a, a + 5, 0) - a << endl; // 1 返回小于等于0的值的个数 //没找到的的话返回数组末地址 cout << upper_bound(a, a + 5, 0) - a << endl; // 2 返回小于等于0的值的个数 return 0; }
cpp输出小数点后位
cout << fixed << setprecision(p) << ans << endl;
高效的比较函数的写法
inline bool cmps(const int &a, const int &b) { return p[a].y < p[b].y; }
scanf整行读入
scanf("%[^\n]%*c",str); //读到'\n'结束读取
read
inline ll read() { ll s = 0, f = 1; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < 48 || ch > 57); while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * f; }
qkpow/matpow
ll qkpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans; } ll getInv(ll a) { return qkpow(a, mod - 2); } //求一个数的逆元
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll mod = 1e9 + 7; struct node { //矩阵类 ll matrix[2][2]; node() { memset(matrix, 0, sizeof(matrix)); } //初始化 void one() { matrix[0][0] = 1; matrix[1][1] = 1; } //单位矩阵E node operator*(node other) { //对*重载 定义矩阵乘法 node ans; //记录乘积 for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { ans.matrix[i][j] += matrix[i][k] * other.matrix[k][j]; ans.matrix[i][j] %= mod; } return ans; } }; node power(node a, ll b) { //快速幂 矩阵a的b次方 node res; res.one(); //单位矩阵 while (b) { if (b & 1) res = res * a; a = a * a; b >>= 1; } return res; } int main() { node temp; temp.matrix[0][0] = 3; temp.matrix[0][1] = 1; temp.matrix[1][0] = 1; temp.matrix[1][1] = 3; ll n, flag = 0; while (cin >> n) cout << power(temp, n).matrix[0][0] << endl; return 0; }
vector
#include <vector> #include <algorithm> #include <iostream> using namespace std; int main() { vector<int>a;//创建了一个int类型的容器 vector<int>b(5);//创建了一个含有五个元素的int类型的容器 vector<int>c(5,0); //这五个元素的值为0 int b1[]= {1,2,3,4,5}; vector<int>b2(b1,b1+4);//不包括第五个元素 int x; for(int i=0; i<5; i++) { cin>>x; a.push_back(x); }//逐个输入 a.pop_back();//删除最后一个元素 int len=a.size();//得到元素个数 sort(a.begin(),a.end()); int *p=find(a.begin(),a.end(),3); //查找值为3的元素 返回迭代器 和string的不一样 string的返回下标 a.swap(b);//ab元素整体交换 感觉没必要 直接swap(a,b)不就好了吗 a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4 a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5 a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6) //如b为1,2,3,4,5,9,8,插入元素后为1,4,5,9,2,3,4,5,9,8 a.resize(10); //将a的现有元素个数调至10个,多则删,少则补,其值随机 a.resize(10,2); //将a的现有元素个数调至10个,多则删,少则补,其值为2 //遍历 和数组一样用下标遍历比用迭代器遍历要快 //迭代器的本质类似指针 vector<int>::iterator it;//声明一个迭代器,来访问vector容器,作用:遍历或者指向vector容器的元素 for(it=a.begin(); it!=a.end(); it++) { cout<<*it<<" "; } vector<vector<int>>aa(5,vector<int>6);//五行六列的二位数组 bool flag=a.empty(); //是否为空 v.insert(lower_bound(v.begin(), v.end(), a[i]), a[i]);//维护一个有序数组并且插在前面 a.clear();//清除 return 0; }
算法竞赛之路 文章被收录于专栏
整理、记录算法竞赛的好题