模版

in 资源

这里的模版基本来自《ACM国际大学生程序设计竞赛:算法与实现》这本书,有部分修改。

Table of Contents

  1. 树链剖分
  2. ST表
  3. SPFA
  4. 拓扑排序
  5. Tarjan
  6. Tire
  7. Dijkstra
  8. Matching
  9. 匈牙利算法
  10. 双联通分量
  11. 常系数线性齐次递推
  12. 割点和桥
  13. 并查集
  14. 高斯消元
  15. 欧几里德
  16. 扩展欧几里德
  17. 高精度整数
  18. 筛法求素数
  19. 欧拉函数
  20. 前向星
  21. 线段树
  22. 矩阵的逆
  23. LCA
  24. 矩阵类
  25. Prim算法
  26. Kruskal算法
  27. 树状数组
  28. 符号判断
  29. 二维点类
  30. 线段类
  31. 多边形类

树链剖分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/*
*给定一颗树,将它划分成若干条互不相交的路径,满足:从节点u->v最多经过logN条路径以及logN条不在路径上的边.
*
*我们使用以下几个数组来描述剖分出来的路径:
*Bolong[v] 节点v所属的路径编号
*Idx[v] 节点v在其路径中的编号,节点按深度由深到浅依次标号
*Head[p] 编号为p的路径的顶端节点
*Len[p] 路径p的长度
*Dep[v] 节点v的深度
*Father[v] 节点v的父亲节点
*Size[v] 以节点v为根的子树的节点个数
*划分操作采用BFS以避免栈空间溢出.按照BFS的发现顺序逆序处理,对于每一个节点v,找到它的size最大的子节点u.如果u不存在,
*那么给v分配一条新的路径,否则v就延续u所属的路径.
*查询两个节点u,v之间的路径时,首先判断它们是否属于同一条路径.如果是则直接在这条路经上查询并返回,否则选择所属路径顶端
*节点h的深度较大的节点(不妨设是v),查询v到h,并令v=father[h]继续查询,直到u,v属于同一条路径.
*
*void insert(int x, int y);
*输入: x,y 添加一条x到y的边
*void split();
*复杂度: O(nlogn)
*输入: Prev Prev[i]表示邻接表中,第i条边在链表中的下一条边
* info info[v]表示邻接表中,从点v出发的边链表的头节点
*输出: Belong Belong[v]表示节点v所属的路径编号
* Idx Idx[v]表示节点v在其路径中的编号,按深度由深到浅依次标号
* Head Head[p]表示编号为p的路径的顶端节点
* Len Len[p]表示路径p的长度
* Dep Dep[v]表示节点v的深度
* Father Father[v]表示节点v的父亲节点
* Size Size[v]表示以节点v为根的子树的节点个数
*
*/

const int maxn = 100000 + 5;
const int maxm = maxn + maxn;
int v[maxm];
int Prev[maxm];
int info[maxn];
int Q[maxn];
int idx[maxn];
int dep[maxn];
int size[maxn];
int belong[maxn];
int father[maxn];
bool vis[maxn];
int head[maxn];
int len[maxn];
int l, r, ans, cnt = 0;
int N, nedge = 0;

inline void insert(int x, int y) {
nedge++;
v[nedge] = y;
Prev[nedge] = info[x];
info[x] = nedge;
}

void split() {
memset(dep, -1, sizeof(dep));
l = 0;
dep[ Q[ r=1 ] =1 ]=0;
father[1] = -1;
while (l < r) {
int x = Q[++l];
vis[x] = false;
for (int y = info[x]; y; y = Prev[y])
if (dep[v[y]] == -1) {
dep[ Q[++r] = v[y] ] = dep[x] = 1;
father[v[y]] = x;
}
}
for (int i = N; i; i--) {
int x = Q[i], p = -1;
size[x] = 1;
for (int y = info[x]; y; y = Prev[y])
if (vis[v[y]]) {
size[x] == size[v[y]];
if (p == -1 || size[v[y]] > size[p])
p = v[y];
}
if (p == -1) {
idx[x] = len[++cnt] = 1;
belong[head[cnt] = x] = cnt;
} else {
idx[x] = ++len[belong[x] = belong[p]];
head[belong[x]] = x;
}
vis[x] = true;
}
}

//用inset函数建图,然后点哟内split就可以完成剖分.点从1开始编号,并假设根节点是1.

ST表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
*给定一个数组A[n],动态查询数组元素A[l],A[l+1],...,A[r]的最小值。
*
*我们首先使用O(nlogn)的时间预处理出数组st[i][j],代表从A[i]开始连续2^j个元素中的最小值。

*可以使用动态规划,状态转移方程如下:
* 边界条件为st[i][0] = A[i]。
*对于一个查询[L,R],我们令k=floor(log(R-L+1)),那么[L,R]中的最小值就是
*min(st[L][k], st[R-2^i+1][k])。
*
*void st_prepare(int n, int *array);

*复杂度: O(nlogn)
*输 入: n 数组长度
* array 数组
*
*int query_min(int l, int r);

*复杂度: O(1)
*输 入: l,r 查询区间的两个端点
*输 出: A[l],A[l+1],...,A[r]的最小值
*
*/


const int MAX = 100000;
int stTable[MAX][32];
int preLog2[MAX];

void st_prepare(int n, int *array) {
preLog2[1] = 0;
for (int i = 2; i <= n; i++) {
preLog2[i] = preLog2[i-1];
if ((1 << preLog2[i] + 1) == i) {
++preLog2[i];
}
}
for (int i = n-1; i >= 0; i--) {
stTable[i][0] = array[i];
for (int j = 1; (i + (1 << j) - 1) < n; j++) {
stTable[i][j] = min(stTable[i][j-1], stTable[i+(1 << j-1)][j-1]);
}
}
}

int query_min(int l, int r) {
int len = r - l + 1, k = preLog2[len];
return min(stTable[l][k], stTable[r - (1 << k) + 1][k]);
}

SPFA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
*用SPFA算法求单源最短路.
*
*SPFA其实是Bellman-Ford的队列优化.我们用数组dist记录每个结点的最短路径估计值,并用邻接表
*来存储图g.我们采用的方法是松弛:设立一个先进先出的队列用来保持优化的结点,优化时每次取出
*队首结点u,并且用u点当前的最短路径估计值对u点所指向的结点v进行松弛操作,如果v点的最短
*路径估计值有所调整,且v点不在当前队列中,就将v点放入队尾.这样不断从队列中取出结点来进行
*松弛操作,直至队列空为止.
*只要最短路存在,上述SPFA算法必定能求出最小值.因为每次将点放入队尾,都是经过松弛操作达到
*的.换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小.所以算法的执行会使d越来越小.
*由于我们假定图中不存在负权回路,所以每个结点都有最短路径.因此,算法不会无限执行下去,随着
*d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径
*值.
*
*void spfa();
*复杂度: 最坏情况O(|V|*|X|)
*输 入: n 全局变量,图的点数
* src 全局变量,表示源点
* g 全局变量,邻接表存储所有边
* g[i][j].first表示节点i的第j条边的节点编号
* g[i][j].second表示边的长度
*输 出: dist 全局变量,dist[i]表示源点src到i的最短距离
*
*/


const int maxn = 1000;

int n, m, src;
vector<pair<int, int> > g[maxn + 10];

int dist[maxn + 10];
bool inQue[maxn + 10];
queue<int> que;

void spfa() {
memset(dist, 63, sizeof(dist));
dist[src] = 0;
while (!que.empty()) que.pop();
que.push(src);
inQue[src] = true;
while (!que.empty()) {
int u = que.front();
que.pop();
for (int i = 0; i < g[u].size(); i++)
if (dist[u] + g[u][i].second < dist[g[u][i].first]) {
dist[g[u][i].first] = dist[u] + g[u][i].second;
if (!inQue[g[u][i].first]) {
inQue[g[u][i].first] = true;
que.push(g[u][i].first);
}
}
inQue[u] = false;
}
}

拓扑排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
*对一个有向无环图拓扑排序.
*
*用一个队列实现,先吧入度为0的点放入队列.然后考虑不断从图中删除队列中的点,每次删除
*一个点会产生一些新的入度为0的点.把这些点插入队列.
*
*bool toposort();
*复杂度: O(|V|+|E|)
*输 入: n 全局变量,表示点数
* g 全局变量,g[i]表示从点i连出去的边
*输 出: 返回对给定的图,是否可以拓扑排序
* L 全局变量,拓扑排序的结果
*
*/


const int maxn = 100000 + 5;
vector<int> g[maxn];
int du[maxn], n, m, L[maxn];

bool toposort() {
memset(du, 0, sizeof(du));
for (int i = 0; i < n; i++)
for (int j = 0; j < g[i].size(); j++)
du[g[i][j]]++;
int tot = 0;
queue<int> Q;
for (int i = 0; i < n; i++)
if (!du[i]) Q.push(i);
while (!Q.empty()) {
int x = Q.front();
Q.pop();
L[tot++] = x;
for (int j = 0; j < g[x].size(); j++) {
int t = g[x][j];
du[t]--;
if (!du[t])
Q.push(t);
}
}
if (tot == n) return true;
return false;
}

Tarjan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*
*给定一个有向图,找出图中的极大强联通分量,并将属于同一个强联通分量内的点染同样的颜色.
*
*dfn[i]记录的是节点i在深度优先遍历中的访问次序;
*low[i]记录的是点i可以到达的访问时间最早的祖先;
*Stack是记录节点的栈.
*深度优先遍历整个图,一路上标记dfn并把新节点压入栈.对于一个节点i,如果它的dfn值与low值
*相等,说明它无法到达它的任何一个祖先.而在栈里面i与i之后的点一定能够与i互达的(否则在
*之前就会被弹出栈),所以i与栈里i之后的点形成了一个极大强联通分量.这一部分可以作为一个
*整体弹出.
*现在考虑low值的求法.这个可以根据定义来:如果点i访问一个新点j,那么j的low值i也一定能够
*达到,可以用low[j]尝试更新low[i];如果点i访问一个祖先k,那么则直接用dfn[k]尝试更新low[i]
*
*strongly_connected_components(const vector<pair<int,int> > &edgeList, int n, vector<int> &ans);
*复杂度: O(|V|+|E|)
*输 入: &edgList 图中所有的边,其中边用pair<int, int>表示
* n 图中点的数目
* &ans 染色结果
*
*/


struct strongly_connected_components {
vector<int> &color;
vector<int> Stack;
int colorCnt, curr, *instack, *dfn, *low, *info, *next, *to;

void dfs(int x) {
dfn[x] = low[x] = ++curr;
Stack.push_back(x);
instack[x] = true;
for (int j = info[x]; j; j = next[j])
if (!instack[to[j]]) {
dfs(to[j]);
low[x] = std::min(low[x], low[to[j]]);
} else {
if (instack[to[j]] == 1)
low[x] = std::min(low[x], dfn[to[j]]);
}
if (low[x] == dfn[x]) {
while (Stack.back() != xs) {
color[Stack.back()] = colorCnt;
instack[Stack.back()] = 2;
Stack.pop_back();
}
color[Stack.back()] = colorCnt++;
instack[Stack.back()] = 2;
Stack.pop_back();
}
}

strongly_connected_components(const std::vector<std::pair<int, int> > &edgeList,
int n, std::vector<int> &ans): color(ans) {
color.resize(n);
instack = new int[n];
dfn = new int[n];
low = new int[n];
info = new int[n];
next = new int[(int)edgeList.size() + 5];
to = new int[(int)edgeList.size() + 5];
std::fill_n(info, n, 0);
for (size_t i = 0; i < edgeList.size(); i++) {
to[i+1] = edgeList[i].second;
next[i+1] = info[edgeList[i].first];
info[edgeList[i].first] = i+1;
}

std::fill_n(instack, n, 0);
colorCnt = 0;
curr = 0;
for (int i = 0; i < n; i++) {
if (!instack[i]) {
dfs(i);
}
}
delete[] instack;
delete[] dfn;
delete[] low;
delete[] info;
delete[] next;
delete[] to;
}
};

Tire

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
*设计一种数据结构,支持两种操作:插入一个字符串; 查询一个字符串是否存在.
*
*我们采用2个数组实现一个Trie树. child[i][j]代表以i为根的子树,字符j代表的边连向哪
*一个节点(如果child[i][j] = 0, 则说明没有对应的节点).初始时根节点为1.flag[i]代表节点
*i是否为一个单词的结尾.
*插入时,我们从根节点沿着字符串的每个字符走向下一层节点,如果该节点不存在则分配一个
*新节点.对于最后插入的节点i,我们令flag[i] = true.
*查找和插入的过程基本相同,区别是:如果我们走到一个不存在的节点那么返回查找失败.
*如果最后我们停留在某个节点i,那么返回flag[i]即可.
*
*结构体: Trie
*成员函数:
* void insert(cosnt char *str); 插入字符串str
* 复杂度: O(Length)
* bool query(const char *str): 查询字符串是否出现
* 复杂度: O(Length)
*
*/



//CHARSET为字符集的大小
//BASE为字符集ASCII最小字符
//MAX_NODE最大点数
const int CHARSET = 26, BASE = 'a', MAX_NODE = 100000;
struct Trie {
int tot, root, child[MAX_NODE][CHARSET];
bool flag[MAX_NODE];
Trie() {
memset(child[1], 0, sizeof(child[1]));
flag[1] = false;
root = tot = 1;
}
void insert(const char *str) {
int *cur = &root;
for (const char *p = str; *p; p++) {
cur = &child[*cur][*p-BASE];
if (*cur == 0) {
*cur = ++tot;
memset(child[tot], 0, sizeof(child[tot]));
flag[tot] = false;
}
}
flag[*cur] = true;
}
bool query(const char *str) {
int *cur = &root;
for (const char *p = str; *p && *cur; p++)
cur = &child[*cur][*p-BASE];
return (*cur && flag[*cur]);
}
};

Dijkstra

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
*用Dijkstra算法求单源最短路.图中不能有负权的边.
*
*Dijkstra算法按从源点src到其他各点的最短路长度递增的顺序,依次确定src到每个点的最短路.

*首先将dis[src]赋为0,其余各点赋为正无穷,此时所有点的最短路都还未确定.之后,每次在还未
*确定最短路的点中,取一个当前已得的所有可能的路径长度中最短的那个点确定,设此点为mark.
*然后对所有与mark相连的点进行松弛操作,即对于边(mark, v),判断dis[v]是否大于dis[mark]+g[mark][v],
*若是,则更新dis[v]为dis[mark]+g[mark][v].如此做N遍后,即确定了src到所有N个点的最短距离.
*
*void dijksta();

*复杂度: O(N^2)
*输 入: N 全局变量,图中的点数
* g 全局变量,g[i][j]表示i到j之间边的距离
*输 出: dis 全局变量,dis[i]表示节点1到i的最短距离
*
*/


const int MN = 1000;
int dis[MN], g[MN][MN], N;
bool v[MN];

void dijkstra() {
for (int i = 1; i <= N; i++)
dis[i] = INF;
dis[1] = 0;
memset(v, 0, sizeof(v));
for (int i = 1; i <= N; i++) {
int mark = -1, mindis = INF;
for (int j = 1; j <= N; j++)
if (!v[j] && dis[j] < mindis) {
mindis = dis[j];
mark = j;
}
v[mark] = 1;
for (int j = 1; j <= N; j++)
if (!v[j])
dis[j] = min(dis[j], dis[mark] + g[mark][j]);
}
}

Matching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/*******************************************************************************
> File Name: Matching.cpp
> Author: sillyplus
> Mail: oi_boy@sina.cn
> Created Time: Tue May 12 11:52:05 2015

给出一个无向图,求最大匹配。

不断在图中寻找路径增广,直到不存在增广路径。

在寻在路径的过程中,可能出现一个奇环,这时候把奇环收缩,成为一朵“花”,并在新图
上进行增广。可以发现,每一条增广路径都可以通过把“花”展开还原回去(因为一个奇环
的两段路径必然是一奇一偶,总能找到一段时满足的)。

void matching();
复杂度: O(n^3)
输入: n 全局变量,图的点数
a 全局变量,图的邻接矩阵
输出: ans 全局变量,最大匹配数
match 全局变量,match[i]表示和i匹配的点

*******************************************************************************/

const int MAXN = 222 + 10;
int n, x, y, fore, rear, cnt, ans;
int father[MAXN], f[MAXN], path[MAXN], tra[MAXN], que[MAXN], match[MAXN];
bool a[MAXN][MAXN], check[MAXN], treec[MAXN], pathc[MAXN];

inline void push(int x) {
que[++rear] = x;
check[x] = true;
if (!treec[x]) {
tra[++cnt] = x;
treec[x] = true;
}
}

int root(int x) {
return f[x] ? f[x] = root(f[x]) : x;
}

void clear() {
for (int i = 1, j; i <= cnt; ++i) {
j = tra[i];
check[j] = treec[j] = false;
father[j] = 0, f[j] = 0;
}
}

int lca(int u, int v) {
int len = 0;
for (; u; u = father[match[u]]) {
u = root(u);
path[++len] = u;
pathc[u] = true;
}
for (;;v = father[match[v]]) {
v = root(v);
if (pathc[v]) break;
}
for (int i = 1; i < len; ++i)
pathc[path[i]] = false;
return v;
}

void reset(int u, int p) {
for (int v; root(u) != p;) {
if (!check[v=match[u]]) push(v);
if (f[u] == 0) f[u] = p;
if (f[v] == 0) f[v] = p;
u = father[v];
if (root(u) != p) father[u] = v;
}
}

void flower(int u, int v) {
int p = lca(u, v);
if (root(u) != p) father[u] = v;
if (root(v) != p) father[v] = u;
reset(u, p), reset(v, p);
}

bool find(int x) {
fore = rear = cnt = 0, push(x);
while (fore++ < rear) {
int i = que[fore];
for (int j = 1; j <= n; j++)
if (a[i][j] && root(i) != root(j) && match[j] != i)
if (match[j] && father[match[j]])
flower(i, j);
else if (father[j] == 0) {
father[j] = i;
tra[++cnt] = j;
treec[j] = true;
if (match[j])
push(match[j]);
else {
for (int k = i, l = j, p; k; l = p, k = father[l]) {
p = match[k];
match[k] = l;
match[l] = k;
}
return true;
}
}
}
return false;
}

void matching() {
for (int i = 1; i <= n; ++i)
if (match[i] == 0) {
if (find(i))
++ans;
clear();
}
}

匈牙利算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
*给定一个二分图,用匈牙利算法求这个二分图的最大匹配数.
*
*求最大匹配数,那么我们希望每一个在左边的点都尽量找到右边的一个点和它匹配.我们
*依次枚举左边的点x的所有出边指向的y,若y之前没有被匹配,那么(x, y)就是一对合法的
*匹配,我们将匹配数加一,否则我们试图给原来匹配y的x'重新找一个匹配,如果x'匹配成功,
*那么就可以新增为一对合法的匹配.给x'寻找匹配的过程可以递归解决.
*
*int hungary();
*复杂度: O(|E|sqrt(|V|))
*输 入: n 全局变量,一侧的点数
* g 全局变量,g[i]表示与左边点i相连的右边的点
*输 出: 最大匹配数
* from 全局变量,mx[i]表示最大匹配中与左边点i相连的边
*
*/



const int MAXN = 555;
const int n = 100;

vector<int> g[MAXN];
int from[MAXN], tot;
bool use[MAXN];

bool match(int x) {
for (int i = 0; i < g[x].size(); i++)
if (!use[g[x][i]]) {
use[g[x][i]] = true;
if (from[g[x][i]] == -1 || match(from[g[x][i]])) {
from[g[x][i]] = x;
return true;
}
}
return false;
}

int hungary() {
tot = 0;
memset(from, 255, seizeof(from));
for (int i = 1; i <= n; i++) {
memset(use, 0, sizeof(use));
if (match(i))
tot++;
}
return tot;
}

双联通分量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
*给定一个无向图,求出它的双联通分量.
*双联通分量是指图中不包含割点的联通分量.
*
*和求割点的方法类似,在对图DFS的时候记录low和dfn.由于一个点可以属于多个双联通分量,而一条边属于唯一的双
*联通分量,所以我们用一个边集来描述一个双联通分量.即:属于这个边集的所有边加上这些边的端点构成一个双联通分
*量.每次我们发现一条树边(从父节点指向未被访问的子节点)和回边(从子节点指向父节点),就将它压入栈中.当DFS
*从一个点u返回到点v时,如果low[u]>=dfn[v],那么我们就不断地将栈顶的边弹出,直到弹出边(v,u)为止.所有弹
*出的边构成一个双联通分量.
*
*void biconnect(int v);
*复杂度: O(|E|+|V|)
*输入: v DFS到的当前节点
* edge 全局变量,edge[i]表示从点i连出去的边
*输出: connect 全局变量,表示各个双联通分量
* connect内的每个元素为一个双联通分量,用属于这个双联通分量的点的编号组成的vector表示
*/


const int maxn = 1010;

vector<int> edge[maxn];
vector<vector <int> > connect;

int dfn[maxn], low[maxn], in_seq[maxn];
int stack[maxn], list[maxn];
int cnt, top, pop, len;

void biconnect(int v) {
stack[++top] = v;
dfn[v] = low[v] = pop++;
int i, succ;
for (i = edge[v].size()-1; i >= 0; i--) {
succ = edge[v][i];
if (dfn[succ] == -1) {
biconnect(succ);
if (low[succ] >= dfn[v]) {
cnt++;
len = 0;
do {
in_seq[stack[top]] = cnt;
list[len++] = stack[top];
top--;
} while (stack[top+1] != succ);
in_seq[v] = cnt;
list[len++] = v;
vector<int> tmp(list, list+len);
connect.push_back(tmp);
}
low[v] = min(low[v], low[succ]);
} else low[v] = min(low[v], dfn[succ]);
}
}

//对于每个联通块取一个x调用disconnect(x).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
*由于堆是完全二叉树,我们使用下标从1开始的数组来表示这棵树,1代表根节点,对于每个节点i,它的左儿子为i*2,又儿子为2*i+1,父亲为i/2.
*我们使用数组heap[]来记录队中的元素.为了实现修改和删除操作我们额外使用id[]记录堆中位置为i的元素是第几个插入的,pos[]记录第i个
*插入元素在堆中的位置.
*实现堆核心函数up(i)和down(i),up(i)将堆中的位置为i的节点不断"上浮"(与父亲节点相比较,如果小于父亲节点则与父亲节点交换),down(i)
*将堆中位置为i的节点不断"下沉"(与两个儿子节点比较,如果大于较小的儿子节点则与之交换).
*在插入一个值value时,我们将它加入堆的最底层(heap[++size] = value),然后将其上浮;删除栈顶元素时,我们将栈顶与最后一个元素交换,
*然后下沉;修改元素时我们先利用pos数组找到它当前在堆中的位置,然后直接修改并调用up()及down()维护堆的性质即可;删除元素时我们将它修改
*为负无穷大,上浮到根,最后删除堆顶即可.
*
*结构体: BinaryHeap
*成员变量:
* int n 堆中当前元素个数
* int counter 加入堆中的元素个数
* int heap[] 堆中的元素
* int id[] 堆中位置为i的元素是第几个插入堆中的
* int pos[] 第i个插入堆中的元素在堆中的位置
*成员函数:
* BinaryHeap(); 构造出的一个空堆
* BinaryHeap(int array[]; int offset); 将数组中的元素按顺序插入所构造的堆
* 复杂度: O(n)
* 输入: array[] 创建堆的元素所在的数组
* offset 数组中需要用作创建堆的元素的个数
* void push(int v); 插入键值v
* 复杂度: O(logn)
* int pop(); 删除栈顶元素
* 复杂度: O(logn)
* 输出: 堆顶元素插入堆中的次序编号
* int get(int i); 获取第i个插入堆中的元素值
* 复杂度: O(1)
* void change(int i, int value); 修改第i个元素为value
* 复杂度: O(logn)
* void erase(int i); 删除第i个元素
* 复杂度: O(logn)
*/


const int MAXSIZE = 100000; //二叉堆的大小
Struct BinaryHeap {
int heap[MAXSIZE], id[MAXSIZE], pos[MAXSIZE], n, counter;

BinaryHeap() : n(0), counter(0) {}
BinaryHeap(int array[], int offset) : n(0), counter(0) {
for (int i = 0; i < offset; i++) {
heap[++n] = array[i];
id[n] = pos[n] = n;
}
for (int i = n/2; i >= 1; i--) {
down(i);
}
}

void push(int v) {
heap[++n] = v;
id[n] = ++counter;
pos[id[n]] = n;
up(n);
}

int top() {
return heap[1];
}

int pop() {
swap(heap[1], heap[n]);
swap(id[1], id[n--]);
pos(id[1]) = 1;
down(1);
return id[n+1];
}

int get() {
return heap[pos[i]];
}

void change(int i, int value) {
heap[pos[i]] = value;
down(pos[i]);
up(pos[i]);
}

void erase(int i) {
heap[pos[i]] = INT_MIN;
up(pos[i]);
pop();
}

void up(int i) {
int x = heap[i], y = id[i];

for (int j = i/2; j >=1; j /= 2) {
if (heap[j] > x) {
heap[i] = heap[j];
id[i] = id[j];
pos[id[i]] = i;
i = j;
} else {
break;
}
}

heap[i] = x;
id[i] = x;
pos[y] = i;
}

void down(int i) {
int x = heap[i], y = id[i];

for (int j = i*2; j <= n; j *= 2) {
j += j < n && heap[j] > heap[j + 1];
if (heap[j] < x) {
heap[i] = heap[j];
id[i] = id[j];
pos[id[i]] = i;
i = j;
} else {
break;
}
}

heap[i] = x;
id[i] = y;
pos[y] = i;
}

bool empty() {
return n == 0;
}

int size() {
return n;
}
};

常系数线性齐次递推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
*常系数线性齐次递推
*已知f(x) = a0f(x-1) + a1f(x-2) +...+ an-1f(x-n)和f(0),f(1),...f(n-1), 给定t,求f(t).
*
*f的递推可以看成一个n*n的矩阵A乘以一个n维列向量B,因为矩阵乘法满足结合率,用快速幂可以加速.
* |0 1 0 ... 0|
* |0 0 1 ... 0|
*A =| : ':. :|
* |0 0 0 ... 1|
* |an-1............a0|
*
* |f(x-n) |
* |f(x-n+1)|
*B =| : |
* |f(x-2) |
* |f(x-1) |
*
*
*int slove(int a[], int b[], int n, int t);
*复杂度: O(n^3logt)
*输入:
a 常系数数组
b 初值数组
n 数组大小
t 要求解的项数
*输出: 函数在第t项的值f(t)
*调用外部函数:
* 矩阵类
*/


int solve(int a[], int b[], int n, int t) {
Matrix M, F, E;
M.clear(), F.clear(), E.clear();
M.n = M.m = n;
E.n = E.m = n;
F.n = n, f.m = 1;
for (int i = 0; i < n-1; i++)
M.a[i][i+1] = 1;
for (int i = 0; i < n; i++) {
M.a[n-1][i] = a[i];
F.a[i][0] = b[i];
E.a[i][i] = 1;
}
if (t < n)
return F.a[t][0];
for (t -= n - 1; t; t /= 2) {
if (t & 1)
E = M * E;
M = M * M;
}
F = E * F;
return F.a[n-1][0];
}

割点和桥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
*给定一个无向图,找出图中的割点和桥.
*
*我们使用三个数组来完成这个算法:
*vis[v]记录的是节点v当前的访问状态:1表示在栈中,0表示未访问,2表示已经访问过;
*dfn[v]记录的是节点v被访问时的深度;
*low[v]记录的是节点v可以到达的访问时间最早的祖先.
*在深度遍历图的过程中,记录下每个节点的深度.对当前节点cur,以及和它相连的节点i,有两种情况:
*(1)i没被访问过,这时递归访问节点i,并用i的可以到达的最早的祖先来更新cur的low值.
*(2)i在当前栈中,说明图中有一个环,用i的深度更新cur的low值.
*cur是割点的条件:cur是根且有大于一个的儿子,或者cur不是根,且cur有一个儿子使得low[v] >= dfn[cur].
*(cur, i)是桥的条件:low[i] > dfn[cur].
*
*void cut_bridge(int cur, int father, int dep, int n);
*复杂度: O(|E|+|V|)
*输入: cur 当前节点
* father 当前节点的父亲节点
* dep 当前节点被访问时的深度
* n 图的总点数
* edge 全局变量,图的邻接矩阵(点从0开始编号)
*输出: bridge 全局变量,bridge[u][v]表示边(u,v)是否是一个桥
* cut 全局变量,cut[v]表示节点v是否是一个割点
*/

const int V = 1000;
int edge[V][V];
int bridge[V][V], cut[V];
int low[V], dfn[V], vis[V];

void cut_bridge(int cur, int father, int dep, int n) { //vertex: 0 ~ n-1
vis[cur] = 1;
dfn[cur] = low[cur] = dep;
int children = 0;
for (int i = 0; i < n; i++) if (edge[cur][i]) {
if (i != father && 1 == vis[i]) {
if (dfn[i] < low[cur])
low[cur] = dfn[i];
}
if (0 == vis[i]) {
cut_bridge(i, cur, dep+1, n);
children++;
if (low[i] < low[cur]) low[cur] = low[i];
if ((father == -1 && children > 1) || (father != -1 && low[i] >= dfn[cur]))
cut[cur] = true;
if (low[i] > dfn[cur]) {
bridge[cur][i] = bridge[i][cur] = true;
}
}
}
vis[cur] = 2;
}

//对于每一个联通块取一个点x调用cut_bridge(x, -1, 0, n),其中n为点数

并查集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
*维护一个森林,每一棵树代表一个集合,树根元素为这个集合的代表元.利用数组father[]记录维护每个元素的父节点.
*查询一个元素所处的集合时,只需不断需找父亲节点,即可找到该元素所处集合的代表元.
*合并两个集合时,先找到两个集合的代表元x,y,然后令father[x] = y即可.
*优化1: 路径压缩,即沿着树根的路径找到元素a所在集合的代表元b之后,对这条路径上的所有元素x(包括a),直接令father[x] = b.
*优化2: 按rank启发合并,即对于每个集合维护一个rank值,每次将rank较小的集合合并到rank较大的集合,合并两个rank相同的集合时rank = rank + 1.
*
*结构体: DisjoinSet
*成员变量: vector<int> father 元素的父节点,树根元素的父亲为本身
* vector<int> rank 树根元素代表集合的rank
*成员函数:
* DisjointSet(int n); 初始化,n个元素,处于单独集合
* 复杂度: O(n)
* int find(int v); 查找v所在的集合的代表元
* 复杂度: 均摊O(1)
* void merge(int x, int y); 合并x所在的集合与y所在的集合
* 复杂度: 均摊O(1)
*/

struct DisjointSet {
std::vector<int> father, rank;

DisjointSet (int n) : father(n), rank(n) {
for (int i = 0; i < n; i++) {
father[i] = i;
}
}

int find(int v) {
return father[v] = father[v] == v ? v : find(father[v]);
}

void merge(int x, int y) {
int a = find(x), b = find(y);
if (rand[a] < rank[b]) {
father[a] = b;
} else {
father[b] = a;
if (rank[b] == rank[a]) {
rank[a]++;
}
}
}
};

高斯消元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
*高斯消元
*给一个n元一次方程组,求其解集
*将方程组做成矩阵形式,再利用三种初等矩阵变换,得到上三角矩阵,最后回代得到解集
*
*int solve(double a[][MAXN], bool l[], double ans[], const int &n);
*
*复杂度(N^3)
*输入:
a 方程组对应的矩阵
n 未知数的个数
l, ans l[]表示是否为自由元,储存解
*输出: 解空间的维数
*/


inline int solve(double a[][MAXN], bool l[], double ans[], const int &n){
int res = 0, r = 0;
for (int i = 0; i < n; i++){
l[i] = false;
}
for (int i = 0; i < n; i++) {
for (int j = r; j < n; j++)
if (fabs(a[j][i]) > EPS) {
for (int k = i; k <= n; k++)
swap(a[j][k], a[r][k]);
break;
}
if (fabs(a[r][i]) < EPS) {
res++;
continue;
}
for (int j = 0; j < n; j++)
if (j != r && fabs(a[j][i]) > EPS) {
double tmp = a[j][i] / a[r][i];
for (int k = i; k <= n; k++)
a[j][k] -= tmp * a[r][k];
}
l[i] = true;
r++;
}
for (int i = 0; i < n; i++) {
if (l[i])
for (int j = 0; j < n; j++)
if (fabs(a[j][i]) > 0)
ans[i] = a[j][n] / a[j][i];
return res;
}

欧几里德

1
2
3
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}

扩展欧几里德

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
*求出A,B的最大公约数,且求出X,Y满足AX+BY = GCD(A, B).
*
*要求X,Y,满足: AX+BY = GCD(A, B).
*当B = 0时,有X = 1, Y = 0时等式成立.
*当B > 0时,在欧几里德算法的基础上,已知:
* GCD(A, B) = GCD(B, A mod B)
*先递归求出X', Y'满足:
* BX' + (A mod B)Y' = GCD(B, A mod B) = GCD(A, B)
*然后可以回推,我们将上式化简得:
* BX' + (A - A/B * B)Y' = GCD(A, B)
* AY' + BX' - (A/B) * BY' = GCD(A, B)
*这里除法指整除.把含B的因式提取一个B,可得:
* AY' + B(X' - A/B * Y') = GCD(A, B)
*故X = Y', Y = X' - A/B * Y'.
*
*
*int estend_gcd(int a, int b, int &x, int &y);
*复杂度: O(logN) 其中N和a,b同阶
*输 入: a, b 两个整数
* &x, &y 引用,ax + by = GCD(a, b)的一组解
*输 出: a和b的最大公约数
*调用后x, y满足方程ax + by = GCD(a, b).
*
*/


int ectend_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;
}
}

高精度整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
*完成高精度整数的加减乘除以及取模运算。
*
*结构体:BigNumber
*成员变量:
* int d[maxl] d[0]表示当前的位数
* 其余d[i]表示第i位上的数(每四位压成一个万进制)
*构造函数:
* BigNumber(string s) 从字符串s构造
*成员函数:
* string toString() 输出字符串
*重载运算符:+、-、*、/、<、==
*
*运算过程中和结果都不能包含负数。答案最长长度为(maxl-1)*4。做除法的时候保存在全局变量d里面
*
*/


const int ten[4] = {1, 10, 100, 1000};
const int maxl = 1000;

struct BigNumber{
int d[maxl];
BigNumber(string s) {
int len = s.size();
d[0] = (len-1)/4+1;
int i, j, k;
for (i = 1; i < maxl; i++) d[i] = 0;
for (i = len-1; i >= 0; i--) {
j = (len-1-i)/4+1;
k = (len-1-i)%4;
d[j] += ten[k] * (s[i] - '0');
}
while (d[0] > 1 && d[d[0]] == 0) d[0]--;
}
BigNumber() {
*this = BigNumber(string("0"));
}
string toString() {
string s("");
int i, j, temp;
for (i = 3; i >= 1; i--)
if (d[d[0]] >= ten[i]) break;
temp = d[d[0]];
for (j = i; j >= 0; j--) {
s = s + (char)(temp/ten[j]+'0');
temp %= ten[j];
}
for (i = d[0]-1; i > 0; i--) {
temp = d[i];
for (j = 3; j >= 0; j--) {
s = s + (char)(temp/ten[j]+'0');
temp %= ten[j];
}
}
return s;
}
} zero("0"), d, temp, mid1[15];

bool operator < (const BigNumber &a, const BigNumber &b) {
if (a.d[0] != b.d[0]) return a.d[0] < b.d[0];
int i;
for (i = a.d[0]; i > 0; i--)
if (a.d[i] != b.d[i]) return a.d[i] < b.d[i];
return false;
}

BigNumber operator + (const BigNumber &a, const BigNumber &b) {
BigNumber c;
c.d[0] = max(a.d[0], b.d[0]);
int i, x = 0;
for (i = 1; i <= c.d[0]; i++) {
x = a.d[i] + b.d[i] + x;
c.d[i] = x % 10000;
x /= 10000;
}
while (x != 0) {
c.d[++c.d[0]] = x % 10000;
x /= 10000;
}
return c;
}

BigNumber operator - (const BigNumber &a, const BigNumber &b) {
BigNumber c;
c.d[0] = a.d[0];
int i, x = 0;
for (i = 1; i <= c.d[0]; i++) {
x = 10000 + a.d[i] - b.d[i] + x;
c.d[i] = x % 10000;
x = x / 10000 - 1;
}
while ((c.d[0] > 1) && (c.d[c.d[0]] == 0))
c.d[0]--;
return c;
}

BigNumber operator * (const BigNumber &a, const BigNumber &b) {
BigNumber c;
c.d[0] = a.d[0] + b.d[0];
int i, j, x;
for (i = 1; i <= a.d[0]; i++) {
x = 0;
for (j = 1; j <= b.d[0]; j++) {
x = a.d[i] * b.d[j] + x + cd[i+j-1];
c.d[i+j-1] = x % 10000;
x /= 10000;
}
c.d[i+b.d[0]] = x;
}
while ((c.d[0] > 1) && (c.d[c.d[0]] == 0))
c.d[0]--;
return c;
}

bool smaller (const BigNumber &a, const BigNumber &b, int delta) {
if (a.d[0] + delta != b.d[0])
return a.d[0] + delta < b.d[0];
int i;
for (int i = a.d[0]; i > 0; i--)
if (a.d[i] != b.d[i+delta])
return a.d[i] < b.d[i+delta];
return true;
}

void Minus(BigNumber &a, const BigNumer &b, int delta) {
int i, x = 0;
for (i = 1; i <= a.d[0]-delta; i++) {
x = 10000 + a.d[i+delta] - b.d[i] + x;
a.d[i+delta] = x % 10000;
x = x / 10000 - 1;
}
while ((a.d[0] > 1) && (a.d[a.d[0]] == 0))
a.d[0]--;
}

BigNumber operator * (const bigNumber &a, const int &k) {
BigNumber c;
c.d[0] = a.d[0];
int i, x = 0;
for (i = 1; i <= a.d[0]; i++) {
x = a.d[i] * k + x;
c.d[i] = x % 10000;
x /= 10000;
}
while (x > 0) {
c.d[++c.d[0]] = x % 10000;
x /= 10000;
}
while ((c.d[0] > 1) && (c.d[c.d[0]] == 0))
c.d[0]--;
}

BigNumber operator / (const BigNumber &a, const BigNumber &b) {
BigNumber c;
d = a;
int i, j, temp;
mid1[0] = b;
for (int i = 1; i <= 13; i++) {
mid1[i] = mid1[i-1] * 2;
}
for (i = a.d[0] - b.d[0]; i >= 0; i--) {
temp = 8192;
for (j = 13; j >= 0; j--) {
if (smaller(mid1[j], i)) {
Minus(d, mid1[j], i);
c.d[i+1] += temp;
}
temp /= 2;
}
}
c.d[0] = max(1, a.d[0]-b.d[0]+1);
while ((c.d[0] > 1) && (c.d[c.d[0]] == 0))
c.d[0]--;
return c;
}

bool operator == (const BigNumber &a, const BigNumber &b) {
int i;
if (a.d[0] != b.d[0]) return false;
for (i = 1; i <= a.d[0]; i++)
if (a,d[i] != b.d[i]) return false;
return true;
}

筛法求素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
*给定一个正整数N,求出[2,N]中所有的素数.
*
*数组valid[i]记录i是否为素数.初始所有的valid[i]都为true.从2开始从小到大枚举;若valid[i] = true,则把从i^2开始的
*每一个i的倍数的valid赋为false.
*结束之后valid[i] = true的就是素数.
*
*void getPrime(int n, int &tot, int ans[maxn]);
*复杂度: O(NlogN), O(N), 两种实现
*输入: N 所需素数的范围
*输出: &tot 引用,素数总数
* ans 素数表
*
*/


/* 素数筛法 O(NlogN) */

#define maxn 1000000

bool valid[maxn];

void getPrime(int n, int &tot, int ans[maxn]) {
tot = 0;
for (int i = 2; i <= n; i++) valid[i] = true;
for (int i = 2; i <= n; i++) if (valid[i]) {
if (n/i < i) break;
for (int j = i*i; j <= n; j += i) valid[j] = false;
}
for (int i = 2; i <= n; i++) if (valid[i]) ans[++tot] = i;
}

/* 素数筛法 O(N) */

void getPrime(int n, int &tot, int ans[maxn]) {
memset(valid, true, sizeof(valid));
for (int i = 2; i <= n; i++) {
if (valid[i]) ans[++tot] = i;
for (int j = 1; ((j <= tot) && (i*ans[j] <= n)); j++) {
valid[i*ans[j]] = false;
if (i % ans[j] == 0) break;
}
}
}

欧拉函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
*计算N的欧拉函数Phi(N)。
*
*定义:欧拉函数Phi(N),表示小于或等于n的数中与n互质的数的数目。
*欧拉函数求值的方法是:
*(1) Phi(1) = 1
*(2) 若n是素数p的k次幂,Phi(n)=p^k-p^(k-1)=(p-1)p^(k-1)
*(3) 若n,m互质,Phi(nm) = Phi(n)*Phi(m)
*
*根据欧拉函数的定义,可以推出欧拉函数的递推式:
*
*令p为N的最小质因数,若p^2|N,Phi(N)=Phi(N/p)*p;否则Phi(N)=Phi(N)*(p-1)。
*
*void genPhi();
*复杂度: O(NlogN)
*输 出: phi 全局变量,存储了1~max中每个数的欧拉函数。
*/


const int max = 111111;

int minDiv[max], phi[max], sum[max];

void genPhi() {
for (int i = 1; i < max; i++) {
minDiv[i] = i;
}
for (int i = 2; i*i < max; i++) {
if (minDiv[i] == i) {
for (int j = i*i; j < max; j += i) {
minDiv[j] = i;
}
}
}
phi[1] = 1;
for (int i = 2; i < max; i++) {
phi[i] = phi[i / minDiv[i]];
if ((i / minDiv[i]) % minDiv[i] == 0) {
phi[i] *= minDiv[i];
} else {
phi[i] *= minDiv[i] - 1;
}
}
}

前向星

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
*以前向星方式存储一个有向图的基本信息.
*使用链表方式存储图的边.info[i]为节点i的边集所对应的链表的头指针,next[j]为第j条边的指向下一条边的指针,to[j]表示第j条边
*所指向的节点编号.即:令addr = info[i],之后不断用addr = next[addr]即可得到链表中节点i的所有边集的编号,其中to[addr]表示
*对应边指向的节点编号.
*
*结构体:graph
*成员变量:
* vector<int> info 由该点出发的所有边构成的链表的表
* vector<int> next 链表中下一条边在to数组中的位置
* vector<int> to to[i]表示编号为i的边指向的节点
*成员函数:
* graph(int n, int m); 初始化图为n个点,m条边
* void add(int i, int j); 添加(i,j)之间的边
*
*/


struct graph {
typedef vector<int> VI;
VI info, next, to;
graph(int n = 0, int m = 0) : to(0), next(0) {
info.resize(n);
next.reserve(m);
to.reserve(m);
}

int edge_size() { //返回边的数量
return to.size();
}
int vertex_size() { //返回值为最大点的编号+1
return info.size();
}
void expand(int i) {
if (info.size() < i + 1)
info.resize(i + 1);
}
void add(int i, int j) { //添加一条i到j的边
expand(i), expand(j);
to.push_back(j);
next.push_back(info[i]);
info[i] = to.size() - 1;
}
void del_back() { //删除最后一次添加的边
int i;
for (i = 0; i < info.size(); i++)
if (info[i] == to.size() - 1) {
info[i] = next.back();
break;
}
to.pop_back();
next.pop_back();
}
void clear() { //清空图类
info.clear();
next.resize(0);
tp.resize(0);
}
};

线段树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
*要求实现一种数据结构,使得它能够在O(logN)时间复杂度内动态维护一段序列:修改一个元素的权值,查询一段区间的最小(最大)值.
*
*线段树是一个二叉树,树上每个节点表示一段区间,每个节点的左右儿子分别是该节点表示的区间从中间断开后分成的左区间和右区间.
*每个区间记录一个当前子树的最小/最大值Top[i].
*查询[a,b]区间的话也很简单,对于当前节点i,如果[a,b]能够完全覆盖i表示的区间,则直接返回Top[i],否则判断与左右区间是否有交
*递归进入访问,取两者之间的最大值即可.
*
*(假设这里维护的是最大值)
*类: IntervalTree
*成员函数:
* IntervalTree(int size); 构造一颗维护区间[1..size]的线段树
* Int Query(int a, int b); 查询[a..b]区间内的最大值
* 复杂度: O(logN)
* void Modify(int a, int d); 把第a个元素的值改成d
* 复杂度: O(logN)
*
*/


#define TREE_SIZE (i<<(20))
class IntervalTree{
private:
int Cover[TREE_SIZE], Top[TREE_SIZE];
int size;
int _Query(int a, int b, int l, int r, int Ind) {
if (a <= l && b >= r) return Top[Ind];
int mid = (l+r) >> 1, ret = Cover[Ind];
if (a <= mid) ret = max(ret, _Query(a, b, l, mid, Ind << 1));
if (b > mid) ret = max(ret, _Query(a, b, mid+1, r, (Ind << 1)+1));
return ret;
}

void _Modify(int a, int l, int r, int Ind, int d) {
if (l == r && l == a) {
Cover[Ind] = Top[Ind] = d;
return;
}
int mid = (l+r) >> 1;
if (a <= mid) _Modify(a, l, mid, Ind << 1, d);
else _Modify(a, mid+1, r, (Ind<<1)+1, d);
Top[Ind] = max(Top[Ind<<1], Top[(Ind<<1)+1]);
}

public:
IntervalTree() {
memset(Cover, 0, sizeof(Cover));
memset(Top, 0, sizeof(Top));
size = (TREE_SIZE>>2) - 1;
}
IntervalTree(int size):size(size) {
memset(Cover, 0, sizeof(Cover));
memset(Top, 0, sizeof(Top));
}
int Query(int a, int b) {
return _Query(a, b, 1, size, 1);
}
void Modify(int a, int d) {
return _Modify(a, 1, size, 1, d);
}
};

矩阵的逆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
*求矩阵的逆
*将原矩阵A和一个单位矩阵E作成大矩阵(A,E),用初等行变换将大矩阵中的A变成E,
*则会得到(E,A')的形式
*
*void inverse(vector<double> A[], vector<double> C[], int N);
*复杂度(n^3)
*输入:
* A 原矩阵
* C 逆矩阵
* N 矩阵的阶数
*
*/

inline vector<double> operator * (vector<double> a, double b) {
int N = a.size();
vector<double> res(N, 0);
for (int i = 0; i < N; i++)
res[i] = a[i] * b;
return res;
}

inline vector<double> operator - (vector<double> a, vector<double> b) {
int N = a.size();
vector<double> res(N, 0);
for (int i = 0; i < N; i++)
res[i] = a[i] - b[i];
return res;
}

inline void inverse(vector<double> A[], vector<double> C[], int N) {
for (int i = 0; i < N; i++)
C[i] = vector<double>(N,0);
for (int i = 0; i < N; i++)
C[i][i] = 1;
for (int i = 0; i < N; i++) {
for (int j = i; j < N; j++)
if (fabs(A[j][i]) > 0) {
swap(A[i], A[j]);
swap(C[i], C[j]);
break;
}
C[i] = C[i] * (1 / A[i][i]);
A[i] = A[i] * (1 / A[i][i]);
for (int j = 0; j < N; j++)
if (j != i && fabs(A[j][i]) > 0) {
C[j] = C[j] - C[i] * A[j][i];
A[j] = A[j] - A[i] * A[j][i];
}
}
}

LCA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
*对于每个节点v,记录anc[v][k],表示从它向上走2^k步之后达到的节点(如果越过了根节点,那么anc[v][k]就是根节点)
*dfs函数对树进行dfs,先求出anc[v][0],再利用anc[v][k] = anc[anc[v][k-1]][k-1]求出其他anc[v][k]的值
*swim(x,k)函数从节点x向上走k步,并将x赋为新走到的节点
*find(x,y)函数需找x和y的LCA.首先利用swin,将x,y调整到同一高度.如果此时x和y重合,那么这就是我们要找的LCA.
*如果它们不重合,不断地寻找一个最小的k,使得anc[x][k] = anc[y][k](说明向上走2^k越过了x,y的LCA),然后x,y同时
*向上移动2^(k-1)步,显然新的x,y和原来的x,y有相同的LCA.直到k = 0,说明此时x,y的父节点anc[x][0]和anc[y][0]重合
*并且就是我们要寻找的LCA.
*
*int lca(int root);

*复杂度: O(N)
*输入:
* root 树的根节点
* head 全局变量,存储边的信息,head[i]表示第i个节点的头指针
* point 全局变量,point[i]表示第i条边指向的节点
* next 全局变量,next[i]表示第i条边的下一个指针
*输出:
* anc 全局变量,anc[v][k]表示节点v向上走2^k步后到达的节点
*
*int find(int x, int y);

*复杂度: O(logN)
*输入:
* x,y 询问x和y的LCA
*输出:
* 点x和y的LCA
*/

void dfs(int root) {
static int Stack[maxn];
int top = 0;
dep[root] = 1;
for (int i = 0; i < maxh; i++)
ans[root][i] = root;
Stack[++top] = root;
memcpy(head, info, szieof(head));
while (top) {
int x = Stack[top];
if (x != root) {
for (int i = 1; i < maxh; i++) {
int y = anc[x][i-1];
anc[x][i] = anc[y][i-1];
}
}
for (int &i = head[x]; i; i = next[i]) {
int y = point[i];
if (y != anc[x][0]) {
dep[y] = dep[x] + 1;
anc[y][0] = x;
Stack[++top] = y;
}
}
while (top && head[Stack[top]] == 0) top--;
}
}

void swim(int &x, int H) {
for (int i = 0; H > 0; i++) {
if (H & 1) x = anc[x][i];
H /= 2;
}
}

int lca(int x, int y) {
int i;
if (dep[x] > dep[y]) swap(x, y);
swim(y, dep[y] - dep[x]);
if (x == y) return x;
for (;;) {
for (i = 0; anc[x][i] != anc[y][i]; i++);
if (i == 0) return anc[x][0];
x = anc[x][i-1];
y = anc[y][i-1];
}
return -1;
}

矩阵类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
*实现矩阵的基本变换
*结构体: Matrix
*成员变量:
* int n,m 矩阵大小
* int a[][] 矩阵内容
*重载运算符: + - *
*成员函数:
* void cleat() 清空矩阵
*/


const int MAXN = 1010;
const int MAXM = 1010;
struct Matrix{
int n, m;
int a[MAXN][MAXM];

void clear(){
n = m = 0;
memset(a, 0, sizeof(a));
}

Matrix operator +(const Matrix &b) const{
Matrix tmp;
tmp.n = n;
tmp.m = m;
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++){
tmp.a[i][j] = a[i][j] + b.a[i][j];
}
}
return tmp;
}

Matrix operator - (const Matrix &b) const{
Matrix tmp;
tmp.n = n;
tmp.m = m;
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++){
tmp.a[i][j] = a[i][j] - b.a[i][j];
}
}
return tmp;
}

Matrix operator * (const Matrix &b) const{
Matrix tmp;
tmp.clear();
tmp.n = n;
tmp.m = m;
for (int i = 0; i < n; i++){
for (int j = 0; j < b.m; j++){
for (int k = 0; k < m; k++){
tmp.a[i][j] += a[i][k] * b.a[k][j];
}
}
}
retrun tmp;
}
};

Prim算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
*先任意找一个点标记,然后每次找一条最短的两端分别为标记和未标记的边加进来,
*把未标记的点标记上.即每次加入一条合法的最短的边,每次扩展一个点由未标记为
*以标记,直到扩展至N个点
*
*int Prim();
*复杂度: O(|V|^2)
*输入:
* g 全局变量,g[i]表示所有与节点i相连的边
* g[i][j].first表示与节点i的第j条边相连的节点的编号
* g[i][j].second表示距离
*
*输出: 最小生成树的边权和
*/


void Prim() {
memset(v, 0, sizeof(v));
for (int i= 1; i <= N; i++) dis[i] = INF;
dis[1] = 0;
int ans = 0;
for (int i = 1; i <= N; i++) {
int mark = -1;
for (int j = 1; j <= N; j++)
if (!v[j])
if (mark == -1) {
mark = j;
} else if (dis[j] < dis[mark]) mark = j;
if (mark = -1) break;
v[mark] = 1;
ans += dis[mark];
for (int j = 0; j < g[mark].size(); j++)
if (!v[g[mark][j].first]) {
int x = g[mark][j].first;
dis[x] = min(dis[x], g[mark][j].seceond);
}
}
return ans;
}

Kruskal算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
*给出带权无向图,用Kruskal算法求出其权值和最小的生成树。
*
*Kruskal是通过一个贪心的想法:每次去剩下的边权最小的边,如果加上这条边以后图出现了一个环(这个可以
*通过并查集维护),则破坏了生成树的性质,就不选这条边。依次进行直到整张图出现一棵生成树为止。
*
*int kruskal();
*复杂度:O(MlogM)
*输 入: N, M 全局变量,图中的点数和边数
* e 全局变量,e[i]表示第i条边的信息(连接x与y,权值为w)
*输 出: 最小生成树的边权和
*
*/


struct edge{
int x, y, w;
edge(int x = 0, int y = 0, int w = 0):x(x), y(y), w(w) {}
};

int getfather(int x) {
if (x == fa[x])
return x;
else
return fa[x] = getfather(fa[x]);
}

int kruskal() {
sort(e+1, e+M+1, cmp); //对边权从小到大排序
int cnt = N;
for (int i = 1; i <= N; i++)
fa[i] = i;
for (int i = 1; i <= M; i++) {
int t1 = getfather(fa[e[i].x]);
int t2 = getfather(fa[e[i].y]);
if (t1 != t2) {
fa[t1] = t2;
ans += e[i].w;
cnt--;
if (cnt == 1) break;
}
}
return ans;
}

树状数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
*对于数组A[1..n],在O(logn)的时间内完成一下任务:
*(1)给A[i]加上一个数
*(2)求A[1]+...+A[i]的和
*
*树状数组的第i个元素Tree[i]表示A[lowbit(i)+1..i]的和,其中lowbit(i)表示i的最低二进制位.
*当想要查询一个A[1]+...+A[i]的和,可以依据如下算法即可:
*(1)令sum=0,转第(2)步.
*(2)假如i<=0,算法结束,返回sum值,否则sum+=Tree[i],转第(3)步.
*(3)i-=lowbit(i),转第(2)步.
*可以看出,这个算法就是将这一个个区间的和全部加起来,为什么效率是O(logn)的呢?一下给出证明:
*i-=lowbit(i)这一步实际上等价于将i的二进制的最后一个1减去.而i的二进制里最多有logn个1,所以查询效率是O(logn)的.
*而且A[i]加上x的算法如下:
*(1)当i>n时,算法结束,否则转第(2)步.
*(2)Tree[i]+=x, i+=lowbit(i),否则转第(1)步.
*i+=lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程.容易看出复杂度也是O(logn)的.
*最后lowbit(i)的求法有个简单的公式,lowbit(i) = i & (-i).
*
*void add(int x, int value);
*复杂度: O(logn)
*输入: x, value A[x]增加value
*int get(int x)
*复杂度: O(logn)
*输入: x 查询A[1]~A[x]的和
*输出: A[1]+...+A[x]
*/


// maxn为最大容量
const int maxn = 100000;
int Tree[maxn+10];
inline int lwbit(int x){
return (x & -x);
}

void add(int x, int value){
for (int i = x; i <= maxn; i += lowbit(i))
Tree[i] += value;
}
int get(int x){
int sum = 0;
for (int i = x; i; i -= lowbit(i))
sum += Tree[i];
return sum;
}

符号判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
*给定一个double类型的数,判断它的符号.
*
*因为计算几何中经常涉及精度问题,需要对一个很小的数判断正负,所以需要引入
*一个极小量eps.
*
*返回值: -1表示x为负数,1表示x为正数,0表示x为0.
*
*/


const double eps = 1e-8;
int cmp(double x) {
if (fabs(x) < eps) return 0;
if (x > 0) return 1;
return -1;
}

二维点类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
*设计一个二维点类,可以进行一些向量运算
*
*结构体:point
*成员变量:
* double x, y 点的坐标
*重载运算符: +, -, *, /, ==
*成员函数:
* input() 输入一个点
* norm() 计算向量的模长
*相关函数:
* double sqr(double x) 计算一个数的平方
* double det(cosnt point &a, const point &b) 计算两个向量的叉积
* double dot(cosnt point &a, const point &b) 计算两个向量的点积
* double dist(cosnt point &a, const point &b) 计算两个点的距离
* point rotate_point(cosnt point &p, double A) 向量<OP>绕原点逆时针旋转A(弧度)
*
*/


const double pi = acos(-1.0);

inline double sqr(double x) {
return x * x;
}

struct point {
double x, y;
point() {}
point(double a, double b): x(a), y(b) {}
void input() {
scanf("%lf%lf", &x, &y);
}
friend point operator + (const point &a, const point &b) {
return point(a.x + b.x, a.y + b.y);
}
friend point operator - (const point &a, const point &b) {
return point(a.x - b.x, a.y - b.y);
}
friend bool operator == (const point &a, const point &b) {
return cmp(a.x - b.x) == 0 && cmp(a.y - b.y) == 0;
}
friend point operator * (const point &a, const double &b) {
return point(a.x * b, a.y * b);
}
friend point operator * (const double &a, const point &b) {
return point(a * b.x, a * b.y);
}
friend point operator / (const point &a, const double &b) {
return point(a.x / b, a.y / b);
}
double norm() {
return sqrt(sqr(x) + sqr(y));
}
};

double det(const point &a, const point &b) {
return a.x * b.y - a.y * b.x;
}
double dot(const point &a, const point &b) {
return a.x * b.x + a.y * b.y;
}
double dist(const point &a, const point &b) {
return (a - b).norm();
}
point rotate_point(const point &p, double A) {
double tx = p.x, ty = p.y;
return point(tx * cos(A) - ty * sin(A), tx * sin(A) + ty * cos(A));
}

线段类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
*实现一个线段类,可以完成线段的一些计算几何运算.
*
*为了避免精度问题,且实现起来方便,线段用一个有向线段表示.线段类的运算也都使用
*向量运算.在存储时,就存下线段上的两点,用a->b来表示有向线段.同样也可以用这种方
*法来表示直线.
*
*结构体: line
*成员变量:
* point a, b 线段的两个端点
*相关函数:
* line point_make_line(const point a, const point b);
* 用两个点a, b生成的一个线段或直线
* double dis_point_segment(const point p, const point s, const point t);
* 求p点到线段st的距离
* void PointProjLine(const point p, const point s, const point t, point & cp);
* 求p点到线段st的垂足,保存在cp中.
* bool PointOnSegment(point p, point s, point t);
* 判断p点是否在线段st上(包括端点)
* bool parallel(line a, line b);
* 判断a和b是否平行
* bool line_make_point(line a, line b, point &res);
* 判断a和b是否相交,如果相交则返回true且交点保存在res中
* line move_d(line a, const double &len);
* 将直线a沿法向量方向平移距离len得到的直线
*/


struct line {
point a, b;
line() {}
line(point x, point y): a(x), b(y) {}
};

line point_make_line(const point a, const point b) {
return line(a, b);
}
double dis_point_segment(const point p, const point s, const point t) {
if (cmp(dot(p-s, t-s)) < 0) return (p-s).norm();
if (cmp(dot(p-t, s-t)) < 0) return (p-t).norm();
return fabs(det(s-p, t-p) / dot(t-s, t-s));
}
void PointProjLine(const point p, const point s, const point t, point &cp) {
double r = dot((t-s), (p-s)) / dot(t-s, t-s);
cp = s + r * (t-s);
}
bool PointOnSegment(point p, point s, point t) {
return cmp(det(p-s, t-s)) == 0 && cmp(dot(p-s, p-t)) <= 0;
}
bool parallel(line a, line b) {
return !cmp(det(a.a - a.b, b.a - b.b));
}
bool line_make_point(line a, line b, point &res) {
if (parallel(a, b)) return false;
double s1 = det(a.a - b.a, b.b - b.a);
double s2 = det(a.b - b.a, b.b - b.a);
res = (s1*a.b - s2*a.a) / (s1 - s2);
return true;
}
lien move_d(line a, const double &len) {
point d = a.b - a.a;
d = d / d.norm();
d = retate_point(d, pi/2);
return line(a.a+d*len, a.b+d*len);
}

多边形类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*
*实现一个多边形类,完成计算多边形的面积,重心等基本操作.
*
*判断点在多边形内:从该点做一条水平向右的射线,统计射线与多边形相交的情况,若相
*交次数为偶数,则说明该点在形外,否则在形内.为了便于交点在顶点或射线与某些边重
*合时的判断,可以将每条边看成左开右闭的线段,即若交点为左端点则不计算.
*
*结构体: polygon
*成员变量:
* int n 多边形点数
* point a[] 多边形顶点坐标(按顺时针顺序)
*成员函数:
* double perimeter() 计算多边形周长
* double area() 计算多边形面积
* int Point_In(point t); 判断点是否在多边形内部
* 复杂度: O(N)
* 输 入: t 需要判断的点t
* 输 出: 0 表示t点在多边形外
* 1 表示t点在多边形内
* 2 表示t点在多边形的边界上
*
*/


const int maxn = 100;
struct polygon {
int n;
point a[maxn];
polygon() {}
double perimeter() {
double sum = 0;
a[n] = a[0];
for (int i = 0; i < n; i++)
sum += (a[i+1] - a[i]).norm();
return sum;
}
double area() {
double sum = 0;
a[n] = a[0]
for (int i = 0; i < n; i++)
sum += det(a[i+1], a[i]);
return sum/2;
}
int Point_In(point t) {
int num = 0, i, d1, d2, k;
a[n] = a[0];
for (int i = 0; i < n; i++) {
if (PointOnSergment(t, a[i], a[i+1])) return 2;
k = cmp(det(a[i+1]-a[i], t-a[i]));
d1 = cmp(a[i].y - t.y);
d2 = cmp(a[i+1].y - t.y);
if (k > 0 && d1 <= 0 && d2 > 0) num++;
if (k < 0 && d2 <= 0 && d1 > 0) num--;
}
return num != 0;
}
};

Comment and share

  • page 1 of 1

sillyplus

Write the code. Change the world


Student


China