最优化剪枝
:求最优值时,当前的状态无论如何不可能比最优值更优,则退出可行性剪枝
:提前判断该状态是否能得到可行解,如不能则退出记忆化搜索
:对于已经搜索过的状态直接退出改变搜索顺序
:对于看起来希望更大的决策先进行搜索IDA*
算法hash
, 二叉排序树
双向广搜
或启发式搜索
定义
: 一个有n个元素的排列,若排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。
经典问题
:
错排公式
:
当n很大时计算就不方便, 一个供参考的简化后的公式是
D(n) = [n! / e + 0.5] (e是自然对数的底, [x]为x的整数部分)
设a是一个整数, p是一个素数, 则 ap ≡ a (mod p)
若a不是p的倍数, 该定理也可以写成 ap-1 ≡ 1 (mod p)
根据费马小定理
: 设p是质数, 且 a, p互质(GCD(a, p) = 1), 则 ap-1 ≡ 1 (mod p)
逆元
:若 ab ≡ 1 (mod p), 则在 mod p 意义下, a, b互为逆元
a存在 mod p 意义下的乘法逆元的充要条件是 GCD(a, p) = 1
所以若p是质数, 且GCD(a, p) = 1, 则a的逆元是 ap-2
逆元作用
:mod 意义下进行的除法, 乘a的逆元等同于除以a.
欧拉函数
: 对正整数n, 欧拉函数
是指小于或等于n的正整数中与n互质的数的数目
欧拉定理
: 若 p, a 为正整数, 且 p, a 互质(GCD(a, p) = 1), 则 aφ(p) ≡ 1(mod p) (φ(p)欧拉函数)
可以看出, 当 p 为质数时, φ(p) = p - 1(1~(p-1)均与p互质), 则 ap-1 ≡ 1(mod p), 就是费马小定理
, 费马小定理其实是欧拉定理的一个子集.
公式
: φ(x) = x * (1 - 1/p1) * (1 - 1 / p2) * (1 - 1 / p3) * (1 - 1 / p4)…(1 - 1 / pn), 其中p1, p2…pn为x的所有质因数, x是不为0的整数.(φ(1)=1)
int Euler(int n) {
int res = n, a = n;
for (int i = 2; i * i <= a; i++) {
if (a % i == 0) {
//trick 先进行除法防止中间数据溢出
res = res / i * (i - 1);
while (a % i == 0) a /= i;
}
}
if(a > 1) res = res / a * (a - 1);
return res;
}
const int maxlen = 1000010;
long long eul[maxlen] = { 0 };
int pri[maxlen] = { 0 }, f[maxlen] = { 0 }, isprim[maxlen] = { 0 }, p = 0;
inline void get_prim() {
memset(isprim, 1, sizeof(isprim));
for (int i = 2; i < maxlen; i++) {
if (isprim[i]) pri[p++] = i;
for(int j = 0; j < p && i * pri[j] < maxlen; j++) {
int k = pri[j] * i;
isprim[k] = 0;
f[k] = pri[j];
if (i % pri[j] == 0) break;
}
}
}
inline void get_eul() {
for (int i = 2; i < maxlen; i++)
if (isprim[i]) {
eul[i] = i - 1;
}
else {
int k = i / f[i];
if (k % f[i] == 0)
eul[i] = eul[k] * f[i];
else
eul[i] = eul[k] * (f[i] - 1);
}
for(int i = 3; i < maxlen; i++)
eul[i] += eul[i - 1];
}
int main() {
int m = 0;
get_prim();
get_eul();
while (scanf("%d", &m))
printf("%lld\n", eul[m] + 1);
}
存在多解 (x, y) ,使 a * x + b * y = GCD(a, b)
int Ex_GCD(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int ans = Ex_GCD(b, a % b, x, y);
int temp = x;
x = y;
y = temp - a / b * y;
return ans;
}
作用
:
扩展欧几里得求出的 x 即为 a 在 mod b 意义下的 乘法逆元。
更严谨
的:
int Get(int a, int b) {
int x = 0, y = 0;
int gcd = Ex_GCD(a, b, x, y);
if (1 % gcd != 0) return -1;
x *= 1 / gcd;
m = abs(m);
int ans = x % m;
if (ans < 0) ans += m;
return ans;
}
int GCD(int m, int n) {
while (m % n != 0) {
int temp = m;
m = n;
n = temp % n;
}
return n;
}
设两个数 m, n 有公式 GCD(m, n) * LCM(m, n) = m * n
bool Judge(int nNum) {
if (nNum == 1) return false;
for (int i = 2; i <= sqrt(nNum); i++) {
if (nNum % i == 0) return false;
}
return true;
}
#include <iostream>
using namespace std;
const long N = 200000;
long prime[N] = { 0 }, num_prime = 0;
int isNotPrime[N] = { 1 };
int main() {
for (long i = 2; i < N; i++) {
if (!isNotPrime[i])
prime[num_prime++] = i;
for (long j = 0 ; j < num_prime && i * prime[j] < N; j++) {
isNotPrime[i * prime[j]] = 1;
if (!(i % prime[j]))
break;
}
}
for (int i = 0; i < num_prime; i++) printf("%d ", prime[i]);
return 0;
}
long long PowerMod(int m, int n, int p) {
long long ans = 1;
while (n > 0) {
if (n & 1) {
ans *= m;
ans %= p;
}
m *= m;
n >>= 1;
m %= p;
}
return ans;
}
(A + B) mod C = (A mod C + B mod C) mod C
(A - B) mod C = (A mod C - B mod C) mod C
(A * B) mod C = (A mod C) * (B mod C) mod C
除法同余使用逆元
结合律
((a + b) mod p + c) mod p = (a + (b + c) mod p) mod p
((a * b) mod p * c) mod p = (a * (b * c) mod p) mod p
分配律
((a + b) mod p * c) mod p = ((a * c) mod p + (b * c) mod p) mod p
对一个已知的自然数, 求解它有多少个因数:
e.g.
求 360 的因数个数:
360 分解质因数表示为: 360 = 23 * 32 * 5
2, 3, 5 的指数分别是 3, 2, 1
360的因数个数可计算出:(3 + 1)(2 + 1)(1 + 1) = 24
求有n个因数的最小自然数:
同样拥有n个因数的自然数可以有多个不同的数, 如何求出这些数中的最小数?
这是和上一个问题问法相反的问题, 是上一题的逆运算.
e.g.
求有24个因数的最小数是多少?
根据上一题思路的启示, 可以这样做:
先将 24 分解因式, 把 24 表示成几个数连乘的形式, 再把这几个数各减去1, 作为质数 2, 3, 5, 7…的指数, 求出这些带指数的数的连乘积, 试算出最小数即可。
具体做法是:
24 = 4 × 6, 24 = 3 × 8, 24 = 4 × 3 × 2
现在分别以这三种表示法试求出目标数x:
综合 1, 2, 3 可知360是有24个因数的最小数.
功能 | 示例 | 位运算 |
---|---|---|
去掉最后一位 | (101101->10110) | x » 1 |
在最后加一个0 | (101101->1011010) | x « 1 |
在最后加一个1 | (101101->1011011) | x « 1 + 1 |
把最后一位变成1 | (101100->101101) | x | 1 |
把最后一位变成0 | (101101->101100) | x | 1 - 1 |
最后一位取反 | (101101->101100) | x ^ 1 |
把右数第k位变成1 | (101001->101101,k=3) | x | (1 « (k - 1)) |
把右数第k位变成0 | (101101->101001,k=3) | x & ~ (1 « (k - 1)) |
右数第k位取反 | (101001->101101,k=3) | x ^ (1 « (k - 1)) |
取末三位 | (1101101->101) | x & 7 |
取末k位 | (1101101->1101,k=5) | x & (1 « k - 1) |
取右数第k位 | (1101101->1,k=4) | x » (k - 1) & 1 |
把末k位变成1 | (101001->101111,k=4) | x | (1 « k - 1) |
末k位取反 | (101001->100110,k=4) | x ^ (1 « k - 1) |
把右边连续的1变成0 | (100101111->100100000) | x & (x + 1) |
把右起第一个0变成1 | (100101111->100111111) | x | (x + 1) |
把右边连续的0变成1 | (11011000->11011111) | x | (x - 1) |
取右边连续的1 | (100101111->1111) | (x ^ (x + 1)) » 1 |
去掉右起第一个1的左边 | (100101000->1000) | x & (x ^ (x - 1)) |
双向最短路
:
删边法
:
每次删最短路上的一条边, 用 Dijkstra+堆
求单源最短路径, 找最优.
定义
: 对有向无环图
的顶点的一种排序, 它使得如果存在一条从顶点A到顶点B的路径, 那么在排序中B出现在A的后面.
限定条件
:
求解思想
:
应用
:
拓扑排序通常用来 “排序” 具有依赖关系的任务.
比如,如果用一个 DAG 来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边表示在做任务 B 之前必须先完成任务 A。故在这个工程中,任意两个任务要么具有确定的先后关系,要么是没有关系,绝对不存在互相矛盾的关系(即环路).
#include <iostream>
#include <list>
#include <queue>
using namespace std;
class Graph {
int V; // 顶点个数
list<int> *adj; // 邻接表
queue<int> q; // 维护一个入度为0的顶点的集合
int* indegree; // 记录每个顶点的入度
public:
Graph(int V); // 构造函数
~Graph(); // 析构函数
void addEdge(int v, int w); // 添加边
bool topological_sort(); // 拓扑排序
};
Graph::Graph(int V) {
this->V = V;
adj = new list<int>[V];
indegree = new int[V];
for(int i = 0; i < V; i++) // 入度全部初始化为0
indegree[i] = 0;
}
Graph::~Graph() {
delete [] adj;
delete [] indegree;
}
void Graph::addEdge(int v, int w) {
adj[v].push_back(w);
indegree[w] ++;
}
bool Graph::topological_sort() {
for (int i = 0; i < V; i++)
if(indegree[i] == 0)
q.push(i); // 将所有入度为0的顶点入队
int count = 0; // 计数,记录当前已经输出的顶点数
while (!q.empty()) {
int v = q.front(); // 从队列中取出一个顶点
q.pop();
cout << v << " "; // 输出该顶点
count ++;
// 将所有v指向的顶点的入度减1,并将入度减为0的顶点入栈
list<int>::iterator beg;
for (beg = adj[v].begin(); beg != adj[v].end(); beg++)
if ((--indegree[*beg]) == 0)
q.push(*beg); // 若入度为0,则入队
}
if(count < V)
return false; // 没有输出全部顶点, 有向图中有回路
else
return true; // 拓扑排序成功
}
//左图 & 右图
int n = 0, m = 0;
int Belong[1001] = { 0 };
int line[1001][1001] = { 0 };
bool used[1001] = { false };
bool find (int nPoint) {
for (int j = 1; j <= m; j++) {
if (line[nPoint][j] && !used[j]) {
used[j] = true;
if (Belong[j] == 0 || find(Belong[j])) {
Belong[j] = nPoint;
return true;
}
}
}
return false;
}
for (int i = 1; i <= n; i++) {
memset(used, 0, sizeof(used));
if(find(i)) all += 1;
}
另一种写法
bool DFS(int nPoint) {
for (int i = head[nPoint]; i != 0; i = EDGE[i].nPre) {
int nNextPoint = EDGE[i].nTo;
if (Is[nNextPoint]) continue;
Is[nNextPoint] = true;
if (nLink[nNextPoint] == 0 || DFS(nLink[nNextPoint])) {
nLink[nNextPoint] = nPoint;
return true;
}
}
return false;
}
int DFN[MAX_N] = { 0 };
int LOW[MAX_N] = { 0 };
int nIndex = 0;
stack<int> S;
bitset<MAX_N> Is;
inline void Tarjan(int nPoint) {
nIndex ++;
DFN[nPoint] = LOW[nPoint] = nIndex;
S.push(nPoint);
Is[nPoint] = 1;
for (int i = head[nPoint]; i != 0; i = EDGE[i].nPre) {
int nNextPoint = EDGE[i].nTo;
if (DFN[nNextPoint] == 0) {
Tarjan(nNextPoint);
LOW[nPoint] = min(LOW[nPoint], LOW[nNextPoint]);
}
else if (Is[nNextPoint] == 1){
LOW[nPoint] = min(LOW[nPoint], DFN[nNextPoint]);
}
}
//根据实际情况处理
if (DFN[nPoint] == LOW[nPoint]) {
int temp = 0;
CC ++;
do {
temp = S.top(); S.pop();
Arr[CC].push_back(temp);
Is[temp] = 0;
} while (temp != nPoint);
nMax_size = max(nMax_size, (int)Arr[CC].size());
}
}
定义
: 如果在 图G 中去掉一个顶点(同时去掉与该点相关联的所有边)后, 该图的连通分支数增加, 就称该顶点为G的割点(cut-vertex)
.
模型
: 给定n对数, 每一对中必须且仅能取一个数, 某些数 i, j 之间有矛盾, 不能同时被取, 求可行性以及一种方案.
思路
:
最优子结构
如果问题的一个最优解中包含了子问题的最优解, 则该问题具有最优子结构。(也称最优化原理)
重叠子问题
在解决整个问题时, 要先解决其子问题, 要解决这些子问题, 又要先解决他们的子子问题… 而这些子子问题又不是相互独立的, 有很多是重复的, 这些重复的子子问题称为重叠子问题.
动态规划正是利用了这种子问题的重叠性质, 对每一个子问题只解一次, 而后将其解保存在一个表中, 以后再遇到这些相同问题时直接查表就可以, 而不需要再重复计算, 每次查表的时间为常数.
无后效性原则
已经求得的状态, 不受未求状态的影响.
求解步骤:
反证法
证明问题具有最优子结构
和无后效性
阶段
动态转移方程
滚动数组
离散化
等手段来优化对于多个关键字的结构体进行sort排序, 关键地进行 comp函数 的编写:
struct STRUCT {
int Num_1;
int Num_2;
int Num_3;
}
//按照 Num_1 Num_2 Num_3 关键字的顺序从小到大排序, 当然也可以按照其他关键字排序, 比如 当 Num_1 相等的时候, Num_2 按照从大到小排序
int comp(const STRUCT A, const STRUCT B) {
if (A.Num_1 < B.Num_1) return 1;
if (A.Num_1 > B.Num_1) return 0;
if (A.Num_2 < B.Num_2) return 1;
if (A.Num_2 > B.Num_2) return 0;
if (A.Num_3 < B.Num_3) return 1;
if (A.Num_3 > B.Num_3) return 0;
return 0;
}