1 数学篇
1.1 最大公约数
- 辗转相除法
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
- 示例:
gcd(4, 6) = 2;
1.1 最小公倍数
a,b的最小公倍数 = a * b / gcd(a, b);
,但 a * b
可能超范围故用:a / gcd(a, b) * b
int lcm(int a, int b)
{
return a / gcd(a, b) * b;
}
- 示例:
lcm(3, 4) = 12;
1.2 素数
- TIPS 1 :
素数:只能整除1和它本身的数(只有1、本身两个正因子)
边界条件: 0 ,1
- 定义法:isPrime()
对一个数n:若它能整数:2~sqrt(n)之间的任一数,就是合数,反之为素数
bool isPrime(int n){
if(n <= 1) return false;
int sqr = sqrt(1.0*n);
for(int i = 2; i <= sqr; ++i){
if(n % i == 0) return false;
}
return true;
}
void FindPrime(int n)
{
for(int i = 1; i <= n; ++i){
if(isPrime(i) == true){
prime.push_back(i);
p[i] = true;
}
}
}
- Eratoshenes筛法:GetPrime()
从2开始(1
和 0
既不是素数也不是合数),X掉(置为 true )所有素数的倍数(合数),剩下的数就是素数:
bool p[101]; //素数标记表要开打点防止越界产生 段错误
vector<int> prime;
void getPrime(int n){
for(int i = 2; i <= n; ++i){
if(p[i] == false){
prime.push_back(i);
for(int j = i + i; j <= n; j += i){
p[j] = true;
}
}
}
}
100以内的素数表:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
1.3 分解质因子
TIPS1:
规则:一个数n不可能存在2个 大于 srqt(n)的因子
步骤:
step 1:GetPrime(sqrt(n));
step 2: 枚举这些prime,如果是因子(if(n % prime[i] == 0))求出该质因子个数:while(n % prime[i]) n /= prime[i];
step 3: 循环结束后若 n != 1 ,说明有比sqrt(n)大的质因子,加入fac
struct Fac
{
int f, cnt;
}fac[20];
GetPrime((int)sqrt(n));
for(int i = 0; i < prime.size(); ++i)
{
int now = prime[i];
if(n % now == 0)
{
fac[idex].f = now;
while(n % now == 0)
{
fac[idex].cnt++;
n /= now;
}
idex++;
}
}
if(n != 1)
{
fac[idex].f = n;
fac[idex++].cnt = 1;
}
1.4 分数四则运算
- TIPS1:
化简规则: 分母:始终为正,分子为0时取1; 约分除gcd
输出规则:分数分为 整数, 假分数, 真分数
- TIPS2:
乘法可能会超范围,使用long long 存分子分母
- 化简
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b); }
Frac Reduce(Frac ans)
{
if(ans.down < 0)
{
ans.up = -ans.up;
ans.down = -ans.down;
}
if(ans.up == 0)
{
ans.down = 1;
}else
{
int d = gcd(abs(ans.up), ans.down);
ans.up /= d;
ans.down /= d;
}
return ans;
}
- 输出:
void ShowAns(Frac ans)
{
ans = Reduce(ans);
if(ans.down == 1)
{
printf("%d", ans.up);
}else if(abs(ans.up) > ans.down)
{
printf("%d %d/%d", ans.up / ans.down, abs(ans.up) % ans.down, ans.down);
}else
{
printf("%d/%d", ans.up, ans.down);
}
}
- 补充:
对于除数非法:补充一个标记位:
struct Frac
{
ll up, down;
bool inf;
Frac() {
inf = false; }
};
Frac Divid(Frac a, Frac b)
{
Frac ans;
if(b.up == 0) ans.inf = true;
ans.up = a.up * b.down;
ans.down = a.down * b.up;
return Reduce(ans);
}
- 完整版:ShowAns();
void ShowAns(Frac a)
{
a = Reduce(a);
if(a.inf) printf("Inf");
else
{
if(a.up < 0) printf("(");
...
if(a.up < 0) printf(")");
}
}
1.5 大整数四则运算
一、用结构体bign实现:
- 存储方式
const int maxn = 1010;
struct Bign
{
int num[maxn];
int len;
Bign()
{
len = 0;
memset(num, 0, sizeof(num));
}
};
- 输入
Bign Change(string n)
{
Bign ans;
ans.len = n.size();
for(int i = 0; i < n.size(); ++i)
{
ans.num[n.size()-1-i] = n[i] - '0';
}
return ans;
}
- 比较
int BignCmp(Bign a, Bign b)
{
if(a.len > b.len) return 1;
else if(a.len < b.len) return -1;
else
{
for(int i = a.len - 1; i >= 0; --i)
{
if(a.num[i] > b.num[i]) return 1;
else if(a.num[i] < b.num[i]) return -1;
}
return 0;
}
}
- 输出:
void ShowBign(Bign a)
{
for(int i = a.len-1; i >= 0; --i) printf("%d", a.num[i]);
}
- 加法:
Bign Add(Bign a, Bign b)
{
Bign ans;
int carry = 0;
for(int i = 0; i < a.len || i < b.len; ++i)
{
int tmp = a.num[i] + b.num[i] + carry;
ans.num[ans.len++] = tmp % 10;
carry = tmp / 10;
}
if(carry != 0) ans.num[ans.len++] = carry;
return ans;
}
- 乘法:
Bign Prod(Bign a, int b)
{
Bign ans;
int carry = 0;
for(int i = 0; i < a.len; ++i)
{
int tmp = a.num[i] * b + carry;
ans.num[ans.len++] = tmp % 10;
carry = tmp / 10;
}
while(carry)
{
ans.num[ans.len++] = carry % 10;
carry /= 10;
}
return ans;
}
- 减法:
Bign Minus(Bign a, Bign b)
{
if(BignCmp(a, b) < 0)
{
printf("-");
swap(a, b);
}
Bign ans;
for(int i = 0; i < a.len; ++i)
{
if(a.num[i] < b.num[i])
{
a.num[i+1]--;
a.num[i] += 10;
}
ans.num[ans.len++] = a.num[i] - b.num[i];
}
while(ans.len - 1 >= 1 && ans.num[ans.len-1] == 0) ans.len--;
return ans;
}
- 除法:
Bign Divid(Bign a, int b, int & r)
{
Bign ans;
ans.len = a.len; //对齐
for(int i = a.len - 1; i >= 0; --i)
{
r = r * 10 + b;
if(r < b) //不够除商0
{
ans.num[i] = 0;
}else //够除商余数除dig
{
ans.num[i] = r / b;
r = r % b; //得到新的余数
}
}
while(ans.len - 1 >= 1 && ans.num[ans.len - 1] == 0) ans.len--;
return ans;
}
二、用string存储
减法不好做:因为借位可能会使0 -> -1,char没法表示-1;
- 对齐:
void Align(string & a, string & b)
{
while(a.size() < b.size()) a.insert(0, "0");
while(b.size() < a.size()) b.insert(0, "0");
}
- 加法:
string Add(string a, string b)
{
Align(a, b);
string ans;
int len1 = a.size(), len2 = b.size(), carry = 0;
for(int i = 0; i < len1 || i < len2; ++i)
{
int da = a[len1-1-i] - '0', db = b[len2-1-i] - '0';
int tmp = da + db + carry;
// ans.insert(0, to_string(tmp % 10 + '0'));
ans += (tmp % 10 + '0');
carry = tmp / 10;
}
if(carry)
{
// ans.insert(0, to_string(carry + '0'));
ans += (carry + '0');
}
reverse(ans.begin(), ans.end());
return ans;
}
- 乘法:
string Prod(string a, int b)
{
string ans;
int len1 = a.size(), carry = 0;
for(int i = 0; i < len1; ++i)
{
int da = a[i] - '0';
int tmp = da * b + carry;
ans += (tmp % 10 + '0');
carry = tmp / 10;
}
while(carry)
{
ans += (carry % 10 + '0');
carry /= 10;
}
reverse(ans.begin(), ans.end());
return ans;
}
- 除法:
string Divid(string a, int b, int & r)
{
string ans = a;
for(int i = 0; i < a.size(); ++i)
{
r = r * 10 + (a[i] - '0');
if(r < b)
{
ans[i] = '0'; //注意这里是字符'0',而不是数字0
}else
{
ans[i] = (r / b + '0');
r = r % b;
}
}
while(ans.size() > 1 && ans[0] == '0') ans.erase(0, 1);
return ans;
}
2. 数据结构篇
2.1 线性表:
TIPS 1 :注意没有有效节点,即空链表,要特殊处理如 PAT 1052
处理思路 1:排序+筛选
- 思路:
Plan 1. 在原链表上直接排序,要额外用标记flg来筛选出有效节点,同时因为排序后下标不在是初始地址,故应额外用add记录源地址。
struct Node{
int add, data, next;
bool flg;
}node[maxn];
bool cmp(Node a, Node b){
if(a.flg != b.flg) return a.flg > b.flg; //true的放前面
else .... //题目规定排序规则
}
输出:
if(cnt == 0) //没有有效节点
else{
for(int i = 0; i < cnt; ++i){
if(i < cnt-1) printf("%05d %d %05d", node[i].add, node[i].data, node[i+1].add);
else printf("%05d %d -1", node[i].add, node[i].data);
}
}
处理思路 2:下标(地址)排序
Plan 2. 将地址(下标),存入一个vector数组,然后下标排序,排序后格式化输出(推广出一种链表题的通法:按题目规则将链表每个节点地址存入vector中,再格式化输出vector)
struct Node{
int data, nex;
}node[maxn];
bool cmp(int add1, int add2){
node[add1].data ? node[add2].data... //排序规则
}
输出:
void Print(vector<int> & ans){
if(ans.size() == 0) //没有有效节点:单独处理
else{
for(int i = 0; i < ans.size(); ++i){
if(i < ans.size()-1) printf("%05d %d %05d", ans[i], node[ans[i]].data, ans[i+1]);
else printf("%05d %d -1", ans[i], node[ans[i]].data);
}
}
}
2.2 树:
树的遍历:
DFS(前中后:递归实现 )【注】 有时间学习下非递归实现方法
- 用vector 记录路径的模板
vector<int> tmp, ans;
void DFS(int id, ...)
{
tmp.push_back(id);
if(遍历到叶节点)
{
//求解,优化各标尺
tmp.pop_back(); //因为下面return回溯到上一层,所以要"手动"将这层的节点pop出去
return;
}
for()
{
DFS(nex, ...);
}
tmp.pop_back(); //遍历完id的所有后继了,id没用了 将pop其出去
}
BFS(层序:队列实现)
TIPS:
STL中的queue,push操作只是push进去一个副本
对原变量的修改不改变队列中的副本
若要修改队列中元素,应push进去元素的编号(如数组下标、变量的地址,即指针),而不是元素本身
- 静态树版:
void BFS(int r)
{
queue<int> q;
q.push(r);
while(!q.empty())
{
int now = q.front();
/*访问当前节点*/
q.pop();
/*子节点入队*/
if(!q.empty()) printf(" "); //可以控制输出空格
}
}
- 普通树版:
void BFS(node* r)
{
queue<node*> q;
q.push(r);
while(!q.empty())
{
node* now = q.front();
/*访问当前节点*/
q.pop();
/*子节点入队*/
if(!q.empty