目录
数据结构
1 字典树
字符串
1 Manacher最长回文子串
2 KMP
3 扩展KMP
4 AC自动机
图论
1 网络流dinic
2 zkw费用流
3 有向图的强联通分量
4 无向图的强联通分量
5 匈牙利匹配
6 最小生成树
7 最短路
8 欧拉回路Fleury
数论
1 中国剩余定理
2 大质数判定
3 约瑟夫环
博弈
1 Bash博弈
2 威佐夫博弈
3 Nim博弈
4 斐波那契博弈
5 Anti-num博弈
6 SG函数
其他
1 祖传头文件
2 大数
3 STL
4 正常的读入挂
5 fread读入挂
6 莫队算法
数据结构
1.字典树
使用时记得先初始化第一个点
const int MAXN = 1e2 + 5;
int Maxlen, Maxnum;
int T, K;
struct node {
int next[27];
int v, num;
void init() {
v=-1;
num = 0;
memset(next,-1,sizeof(next));
}
};
struct node L[1000000];
int tot=0;
void add(char a[]) {
int now=0;
int len = strlen(a);
for(int i=0; i<len; i++) {
int tmp=a[i]-'a';
int next=L[now].next[tmp];
if(next==-1) {
next=++tot;
L[next].init();
L[now].next[tmp]=next;
}
now=next;
L[now].num ++;
if(L[now].num >= 3) {
if(Maxlen < i + 1) {
Maxlen = i + 1;
Maxnum = L[now].num;
} else if(Maxnum < L[now].num && Maxlen <= i + 1) {
Maxnum = L[now].num;
}
}
}
L[now].v=0;
}
int query(char s2[]) {
int len = strlen(s2);
int now = 0;
for(int i = 0; i < len; i++) {
int temp = s2[i] - 'a';
int next = L[now].next[temp];
if(next == -1) return 0;
now = next;
}
return L[now].num;
}
int main()
{
L[0].init();
return 0;
}
字符串
1 Manacher最长回文子串
const int MAX = 200000 + 10;
char s[MAX * 2];//记得要开两倍
int p[MAX * 2];
int manacher(char *s) {
int len = strlen(s), id = 0, ans = 0;
for(int i = len; i >= 0; i--) {
s[i + i + 2] = s[i];
s[i + i + 1] = '#';
}
s[0] = '*';//防越界,很重要!!
for(int i = 2; i < 2 * len + 1; ++i) {
if(p[id] + id > i) p[i] = min(p[2 * id - i], p[id] + id - i);
else p[i] = 1;
while(s[i - p[i]] == s[i + p[i]]) p[i]++;
if(id + p[id] < i + p[i]) id = i;
ans = max(ans, p[i] - 1);
}
return ans;
}
2.KMP
const int MX = 2000 + 5;
/*
int Next[MX], n;
void GetNext() {
Next[0] = 0;
for(int i = 1; i < n; i++) {
int j = Next[i - 1];
while(j && S[i] != S[j]) j = Next[j - 1];
Next[i] = S[i] == S[j] ? j + 1 : 0;
}
}
/*求前缀i 循环节最长长度
int GetCir(int p) {
return (p + 1) % (p - Next[p] + 1) == 0 ? p - Next[p] + 1 : p + 1;
}
*/
/*会有重叠部分*/
int Next[MX];
int KMP(char *A, char *B) {
int m = strlen(A), n = strlen(B);
Next[0] = 0;
for(int i = 1; i < n; i++) {
int k = Next[i - 1];
while(B[i] != B[k] && k) k = Next[k - 1];
Next[i] = B[i] == B[k] ? k + 1 : 0;
}
int ans = 0, j = 0;
for(int i = 0; i < m; i++) {
while(A[i] != B[j] && j) j = Next[j - 1];
if(A[i] == B[j]) j++;
if(j == n) ans++;
}
return ans;
}
3.扩展KMP
/*
* 扩展KMP算法
*/
//next[i]:x[i...m-1]与x[0...m-1]的最长公共前缀
//extend[i]:y[i...n-1]与x[0...m-1]的最长公共前缀
void pre_EKMP(char x[], int m, int next[]) {
next[0] = m;
int j = 0;
while(j + 1 < m && x[j] == x[j + 1])j++;
next[1] = j;
int k = 1;
for(int i = 2; i < m; i++) {
int p = next[k] + k - 1;
int L = next[i - k];
if(i + L < p + 1)next[i] = L;
else {
j = max(0, p - i + 1);
while(i + j < m && x[i + j] == x[j])j++;
next[i] = j;
k = i;
}
}
}
void EKMP(char x[], int m, char y[], int n, int next[], int extend[]) {
pre_EKMP(x, m, next);
int j = 0;
while(j < n && j < m && x[j] == y[j])j++;
extend[0] = j;
int k = 0;
for(int i = 1; i < n; i++) {
int p = extend[k] + k - 1;
int L = next[i - k];
if(i + L < p + 1)extend[i] = L;
else {
j = max(0, p - i + 1);
while(i + j < n && j < m && y[i + j] == x[j])j++;
extend[i] = j;
k = i;
}
}
}
4.AC自动机
struct Trie {
int next[500010][26], fail[500010], end[500010];
int root, L;
int newnode() {
for(int i = 0; i < 26; i++)
next[L][i] = -1;
end[L++] = 0;
return L - 1;
}
void init() {
L = 0;
root = newnode();
}
void insert(char buf[]) {
int len = strlen(buf);
int now = root;
for(int i = 0; i < len; i++) {
if(next[now][buf[i] - 'a'] == -1)
next[now][buf[i] - 'a'] = newnode();
now = next[now][buf[i] - 'a'];
}
end[now]++;
}
void build() {
queue<int>Q;
fail[root] = root;
for(int i = 0; i < 26; i++)
if(next[root][i] == -1)
next[root][i] = root;
else {
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
while( !Q.empty() ) {
int now = Q.front();
Q.pop();
for(int i = 0; i < 26; i++)
if(next[now][i] == -1)
next[now][i] = next[fail[now]][i];
else {
fail[next[now][i]] = next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
int query(char buf[]) {
int len = strlen(buf);
int now = root;
int res = 0;
for(int i = 0; i < len; i++) {
now = next[now][buf[i] - 'a'];
int temp = now;
while( temp != root ) {
res += end[temp];
end[temp] = 0;
temp = fail[temp];
}
}
return res;
}
void debug() {
for(int i = 0; i < L; i++) {
printf("id = %3d,fail = %3d,end = %3d,chi = [", i, fail[i], end[i]);
for(int j = 0; j < 26; j++)
printf("%2d", next[i][j]);
printf("]\n");
}
}
};
Trie AC;
图论
1.网络流Dinic
const int maxn = 405; //开四倍
const int maxe = 4*maxn*maxn;
const int inf = 0x3f3f3f3f;
struct MaxFlow {
struct Edge {
int v, w, nxt;
} edge[maxe];
int head[maxn], tot, level[maxn];
void init(){
memset(head,-1,sizeof(head));
tot=0;
}
void add(int u, int v, int w) {
edge[tot].v = v;
edge[tot].w = w;
edge[tot].nxt = head[u];
head[u] = tot++;
edge[tot].v = u;
edge[tot].w = 0;
edge[tot].nxt = head[v];
head[v] = tot++;
}
bool bfs(int s, int t) {
memset(level, -1, sizeof(level));
queue<int>q;
q.push(s);
level[s] = 0;
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = head[u]; ~i; i = edge[i].nxt) {
if(edge[i].w > 0 && level[edge[i].v] < 0) {
level[edge[i].v] = level[u] + 1;
q.push(edge[i].v);
}
}
}
return level[t] > 0;
}
int dfs(int u, int t, int f) {
if(u == t) return f;
for(int i = head[u]; ~i; i = edge[i].nxt) {
int v = edge[i].v;
if(edge[i].w > 0 && level[v] > level[u]) {
int d = dfs(v, t, min(f, edge[i].w));
if(d > 0) {
edge[i].w -= d;
edge[i ^ 1].w += d;
return d;
}
}
}
level[u] = -1; //不太确定,如果WA了把这句删掉试试
return 0;
}
int solve(int s, int t) {
int flow = 0, f;
while(bfs(s, t)) {
while(f = dfs(s, t, inf)) flow += f;
}
return flow;
}
}F;
2.
zkw费用流
const int MX = 80000;
const int MXE = 4 * MX * MX;
const int ME = 4e5 + 5;//边的数量
struct MCMF {
int S, T;//源点,汇点
int tot, n;
int st, en, maxflow, mincost;
bool vis[MX];
int head[MX], cur[MX], dis[MX];
int roade[MX], roadv[MX], rsz; //用于打印路径
queue <int> Q;
struct Edge {
int v, cap, cost, nxt, flow;
Edge() {}
Edge(int a, int b, int c, int d) {
v = a, cap = b, cost = c, nxt = d, flow = 0;
}
} E[ME], SE[ME];
void init(int _n) {
n = _n, tot = 0;
for(int i = 0; i <= n; i++) head[i] = -1;
}
void edge_add(int u, int v, int cap, int cost) {
E[tot] = Edge(v, cap, cost, head[u]);
head[u] = tot++;
E[tot] = Edge(u, 0, -cost, head[v]);
head[v] = tot++;
}
bool adjust() {
int v, min = INF;
for(int i = 0; i <= n; i++) {
if(!vis[i]) continue;
for(int j = head[i]; ~j; j = E[j].nxt) {
v = E[j].v;
if(E[j].cap - E[j].flow) {
if(!vis[v] && dis[v] - dis[i] + E[j].cost < min) {
min = dis[v] - dis[i] + E[j].cost;
}
}
}
}
if(min == INF) return false;
for(int i = 0; i <= n; i++) {
if(vis[i]) {
cur[i] = head[i];
vis[i] = false;
dis[i] += min;
}
}
return true;
}
int augment(int i, int flow) {
if(i == en) {
mincost += dis[st] * flow;
maxflow += flow;
return flow;
}
vis[i] = true;
for(int j = cur[i]; j != -1; j = E[j].nxt) {
int v = E[j].v;
if(E[j].cap == E[j].flow) continue;
if(vis[v] || dis[v] + E[j].cost != dis[i]) continue;
int delta = augment(v, std::min(flow, E[j].cap - E[j].flow));
if(delta) {
E[j].flow += delta;
E[j ^ 1].flow -= delta;
cur[i] = j;
return delta;
}
}
return 0;
}
void spfa() {
int u, v;
for(int i = 0; i <= n; i++) {
vis[i] = false;
dis[i] = INF;
}
Q.push(st);
dis[st] = 0; vis[st] = true;
while(!Q.empty()) {
u = Q.front(), Q.pop(); vis[u] = false;
for(int i = head[u]; ~i; i = E[i].nxt) {
v = E[i].v;
if(E[i].cap == E[i].flow || dis[v] <= dis[u] + E[i].cost) continue;
dis[v] = dis[u] + E[i].cost;
if(!vis[v]) {
vis[v] = true;
Q.push(v);
}
}
}
for(int i = 0; i <= n; i++) {
dis[i] = dis[en] - dis[i];
}
}
int zkw(int s, int t) {
st = s, en = t;
spfa();
mincost = maxflow = 0;
for(int i = 0; i <= n; i++) {
vis[i] = false;
cur[i] = head[i];
}
do {
while(augment(st, INF)) {
memset(vis, false, n * sizeof(bool));
}
} while(adjust());
return mincost;
}
} M;
3.有向图的强联通分量缩点
const int MX = 5e3 + 5;
const int INF = 0x3f3f3f3f;
struct Edge {
int u, v, nxt;
} E[60005];
int Head[MX], erear;
void edge_init() {
erear = 0;
memset(Head, -1, sizeof(Head));
}
void edge_add(int u, int v) {
E[erear].u = u;
E[erear].v = v;
E[erear].nxt = Head[u];
Head[u] = erear++;
}
int n, m, IN[MX], cnt[MX], val[MX];
int bsz, ssz, dsz;
int Low[MX], DFN[MX];
int belong[MX], Stack[MX];
bool inStack[MX];
void Init_tarjan(int n) {
bsz = ssz = dsz = 0;
for(int i = 1; i <= n; ++i) Low[i] = DFN[i] = 0;
}
void Tarjan(int u) {
Stack[++ssz] = u;
inStack[u] = 1;
Low[u] = DFN[u] = ++dsz;
for(int i = Head[u]; ~i; i = E[i].nxt) {
int v = E[i].v;
if(!DFN[v]) {
Tarjan(v);
Low[u] = min( Low[v], Low[u]);
} else if(inStack[v]) {
Low[u] = min( Low[u], DFN[v]);
}
}
if(Low[u] == DFN[u]) {
++bsz;
int v;
do {
v = Stack[ssz--];
inStack[v] = 0;
belong[v] = bsz;
} while(u != v);
}
}
int solve(int n) {
Init_tarjan(n);
for (int i = 1; i <= n; i++) {
if (!DFN[i]) Tarjan(i);
} edge_init();
for(int i = 0; i < m; i++) {
int u = E[i].u, v = E[i].v;
u = belong[u]; v = belong[v];
if(u != v) {
edge_add(u, v);
}
}
}
4.无向图的强联通分量缩点
const int MX = 1e5 + 10;
struct Edge {
int u, v, nxt;
} E[MX];
int Head[MX], erear;
void edge_init() {
erear = 0;
memset(Head, -1, sizeof(Head));
}
void edge_add(int u, int v) {
E[erear].u = u;
E[erear].v = v;
E[erear].nxt = Head[u];
Head[u] = erear++;
}
int n, m, IN[MX], cnt[MX], val[MX];
int bsz, ssz, dsz;
int Low[MX], DFN[MX];
void Init_tarjan(int n) {
bsz = ssz = dsz = 0;
for(int i = 1; i <= n; ++i) Low[i] = DFN[i] = 0;
}
int Stack[MX], inStack[MX], Belong[MX];
void trajan(int u, int e) {
inStack[u] = 1;
Stack[++ssz] = u;
DFN[u] = Low[u] = ++dsz;
for(int i = Head[u]; ~i; i = E[i].nxt) {
int v = E[i].v;
if((i ^ 1) == e) continue;
if(!DFN[v]) {
trajan(v, i);
Low[u] = min(Low[u], Low[v]);
} else if(inStack[v]) {
Low[u] = min(Low[u], Low[v]);
}
}
if(DFN[u] == Low[u]) {
bsz++; int v;
do {
v = Stack[ssz--];
inStack[v] = 0;
Belong[v] = bsz;
} while(ssz && v != u);
}
}
void tarjan_solve(int n) {
dsz = bsz = ssz = 0;
memset(DFN, 0, sizeof(DFN));
for(int i = 1; i <= n; i++) {
if(!DFN[i]) trajan(i, -1);
}
/*缩点*/
edge_init();
for(int i = 0; i < 2 * m; i += 2) {
int u = E[i].u, v = E[i].v;
u = Belong[u]; v = Belong[v];
if(u == v) continue;
edge_add(u, v);
edge_add(v, u);
}
}
5.匈牙利匹配
/*复杂度O(VE)
最小点覆盖=最大匹配数
最小边覆盖=左右点数-最大匹配数
最小路径覆盖=点数-最大匹配数
最大独立集=点数-最大匹配数
*/
const int MX = 205;
struct Edge {
int v, nxt;
}E[MX];
int Head[MX], tot;
int match[MX];
bool vis[MX];
void edge_init() {
memset(Head, -1, sizeof(Head));
memset(match, 0, sizeof(match));
tot = 0;
}
void edge_add(int u, int v) {
E[tot].v = v;
E[tot].nxt = Head[u];
Head[u] = tot++;
}
bool DFS(int u) {
for(int i = Head[u]; ~i; i = E[i].nxt) {
int v = E[i].v;
if(!vis[v]) {
vis[v] = 1;
if(match[v] == -1 || DFS(match[v])) {
//如果v已经被匹配了,且被匹配的点不是
//当前点u,那么就去修改这个match[v],如果修改成功了,那么就可以返回1
match[v] = u;
return 1;
}
}
}
return 0;
}
int BM(int n) {
int res = 0;
memset(match, -1, sizeof(match));
for(int u = 1; u <= n; u++) {
memset(vis, 0, sizeof(vis));
if(DFS(u)) res++;//找与u匹配的点v,如果找到了答案就+1
}
return res;
}
6.最小生成树
/*
* Kruskal算法求MST
*/
const int MAXN = 1000 + 5; //最大点数
const int MAXM = 50000 + 5; //最大边数
int F[MAXN];//并查集使用
struct Edge {
int u, v, w;
} edge[MAXM]; //存储边的信息,包括起点/终点/权值
int tol;//边数,加边前赋值为0
void addedge(int u, int v, int w) {
edge[tol].u = u;
edge[tol].v = v;
edge[tol++].w = w;
}
bool cmp(Edge a, Edge b) {
//排序函数,讲边按照权值从小到大排序
return a.w < b.w;
}
int find(int x) {
if(F[x] == -1)return x;
else return F[x] = find(F[x]);
}
int Kruskal(int n) { //传入点数,返回最小生成树的权值,如果不连通返回-1
memset(F, -1, sizeof(F));
sort(edge, edge + tol, cmp);
int cnt = 0; //计算加入的边数
int ans = 0;
for(int i = 0; i < tol; i++) {
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
int t1 = find(u);
int t2 = find(v);
if(t1 != t2) {
ans += w;
F[t1] = t2;
cnt++;
}
if(cnt == n - 1)break;
}
if(cnt < n - 1)return -1; //不连通
else return ans;
}
/*
* Prim求MST
* 耗费矩阵cost[][],标号从0开始,0~n-1
* 返回最小生成树的权值,返回-1表示原图不连通
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 110;
bool vis[MAXN];
int lowc[MAXN];
int Prim(int cost[][MAXN], int n) { //点是0~n-1
int ans = 0;
memset(vis, false, sizeof(vis));
vis[0] = true;
for(int i = 1; i < n; i++)lowc[i] = cost[0][i];
for(int i = 1; i < n; i++) {
int minc = INF;
int p = -1;
for(int j = 0; j < n; j++)
if(!vis[j] && minc > lowc[j]) {
minc = lowc[j];
p = j;
}
if(minc == INF)return -1; //原图不连通
ans += minc;
vis[p] = true;
for(int j = 0; j < n; j++)
if(!vis[j] && lowc[j] > cost[p][j])
lowc[j] = cost[p][j];
}
return ans;
}
7.最短路
1、最短路
1.1 Dijkstra单源最短路,邻接矩阵形式
权值必须是非负
/*
* 单源最短路径,Dijkstra算法,邻接矩阵形式,复杂度为O(n^2)
* 求出源beg到所有点的最短路径,传入图的顶点数,和邻接矩阵cost[][]
* 返回各点的最短路径lowcost[], 路径pre[].pre[i]记录beg到i路径上的父结点,pre[beg]=-1
* 可更改路径权类型,但是权值必须为非负
*
*/
const int MAXN = 1010;
#define typec int
const typec INF = 0x3f3f3f3f; //防止后面溢出,这个不能太大
bool vis[MAXN];
int pre[MAXN];
void Dijkstra(typec cost[][MAXN], typec lowcost[], int n, int beg) {
for(int i = 0; i < n; i++) {
lowcost[i] = INF; vis[i] = false; pre[i] = -1;
}
lowcost[beg] = 0;
for(int j = 0; j < n; j++) {
int k = -1;
int Min = INF;
for(int i = 0; i < n; i++)
if(!vis[i] && lowcost[i] < Min) {
Min = lowcost[i];
k = i;
}
if(k == -1)break;
vis[k] = true;
for(int i = 0; i < n; i++)
if(!vis[i] && lowcost[k] + cost[k][i] < lowcost[i]) {
lowcost[i] = lowcost[k] + cost[k][i];
pre[i] = k;
}
}
}
1.2 Dijkstar 算法 +堆优化
/*
* 使用优先队列优化Dijkstra算法
* 复杂度O(ElogE)
* 注意对vector<Edge>E[MAXN]进行初始化后加边
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 1000010;
struct qnode {
int v;
int c;
qnode(int _v = 0, int _c = 0): v(_v), c(_c) {}
bool operator <(const qnode &r)const {
return c > r.c;
}
};
struct Edge {
int v, cost;
Edge(int _v = 0, int _cost = 0): v(_v), cost(_cost) {}
};
vector<Edge>E[MAXN];
bool vis[MAXN];
int dist[MAXN];
void Dijkstra(int n, int start) { //点的编号从1开始
memset(vis, false, sizeof(vis));
for(int i = 1; i <= n; i++)dist[i] = INF;
priority_queue<qnode>que;
while(!que.empty())que.pop();
dist[start] = 0;
que.push(qnode(start, 0));
qnode tmp;
while(!que.empty()) {
tmp = que.top();
que.pop();
int u = tmp.v;
if(vis[u])continue;
vis[u] = true;
for(int i = 0; i < E[u].size(); i++) {
int v = E[tmp.v][i].v;
int cost = E[u][i].cost;
if(!vis[v] && dist[v] > dist[u] + cost) {
dist[v] = dist[u] + cost;
que.push(qnode(v, dist[v]));
}
}
}
}
void addedge(int u, int v, int w) {
E[u].push_back(Edge(v, w));
}
1.3 单源最短路 单源最短路 bellman_ford
/*
* 单源最短路bellman_ford算法,复杂度O(VE)
* 可以处理负边权图。
* 可以判断是否存在负环回路。返回true,当且仅当图中不包含从源点可达的负权回路
* vector<Edge>E;先E.clear()初始化,然后加入所有边
* 点的编号从1开始(从0开始简单修改就可以了)
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 550;
int dist[MAXN];
struct Edge {
int u, v;
int cost;
Edge(int _u = 0, int _v = 0, int _cost = 0): u(_u), v(_v), cost(_cost) {}
};
vector<Edge>E;
bool bellman_ford(int start, int n) { //点的编号从1开始
for(int i = 1; i <= n; i++)dist[i] = INF;
dist[start] = 0;
for(int i = 1; i < n; i++) { //最多做n-1次
bool flag = false;
for(int j = 0; j < E.size(); j++) {
int u = E[j].u;
int v = E[j].v;
int cost = E[j].cost;
if(dist[v] > dist[u] + cost) {
dist[v] = dist[u] + cost;
flag = true;
}
}
if(!flag)return true;//没有负环回路
}
for(int j = 0; j < E.size(); j++)
if(dist[E[j].v] > dist[E[j].u] + E[j].cost)
return false;//有负环回路
return true;//没有负环回路
}
1.4 单源最短路 单源最短路 单源最短路 SPFA
/*
* 单源最短路SPFA
* 时间复杂度 0(kE)
* 这个是队列实现,有时候改成栈实现会更加快,很容易修改
* 这个复杂度是不定的
*/
const int MAXN = 1010;
const int INF = 0x3f3f3f3f;
struct Edge {
int v;
int cost;
Edge(int _v = 0, int _cost = 0): v(_v), cost(_cost) {}
};
vector<Edge>E[MAXN];
void addedge(int u, int v, int w) {
E[u].push_back(Edge(v, w));
}
bool vis[MAXN];//在队列标志
int cnt[MAXN];//每个点的入队列次数
int dist[MAXN];
bool SPFA(int start, int n) {
memset(vis, false, sizeof(vis));
for(int i = 1; i <= n; i++)dist[i] = INF;
vis[start] = true;
dist[start] = 0;
queue<int>que;
while(!que.empty())que.pop();
que.push(start);
memset(cnt, 0, sizeof(cnt));
cnt[start] = 1;
while(!que.empty()) {
int u = que.front();
que.pop();
vis[u] = false;
for(int i = 0; i < E[u].size(); i++) {
int v = E[u][i].v;
if(dist[v] > dist[u] + E[u][i].cost) {
dist[v] = dist[u] + E[u][i].cost;
if(!vis[v]) {
vis[v] = true;
que.push(v);
if(++cnt[v] > n)return false;
//cnt[i]为入队列次数,用来判定是否存在负环回路
}
}
}
}
return true;
}
8.欧拉回路Fleury
/*删边要注意复杂度,尽量别用标记删除,而是直接删除
无向图满足欧拉回路:度为偶数,或者度为奇数的点个数为2
有向图满足欧拉回路:入度全部等于出度,或者1 个点入度-出度=1,一个点出度-入度=1,其他点入度等于出度
*/
void Fleury(int u) {
for(int i = Head[u]; ~i; i = Head[u]) {
Head[u] = E[i].nxt;
if(!vis[i | 1]) {
int v = E[i].v;
vis[i | 1] = 1;
Fleury(v);
}
}
Path[++r] = u;
}
数论
1.中国剩余定理
LL gcd(LL a, LL b, LL &x, LL &y) {
if(b == 0) {
x =1, y = 0;
return a;
}
LL r = gcd(b, a % b, x, y);
LL t = y;
y = x - a / b * y;
x = t;
return r;
}
LL multi(LL a, LL b, LL mod) {
LL ret = 0;
while(b) {
if(b & 1) {
ret = ret + a;
if(ret >= mod) ret -= mod;
}
a = a + a;
if(a >= mod) a -= mod;
b >>= 1;
}
return ret;
}
LL crt(int n, LL m[], LL a[]) {
LL M = 1, d, y, x = 0;
for(int i = 0; i < n; i++) M *= m[i];
for(int i = 0; i < n; i++) {
LL w = M / m[i];
d = gcd(m[i], w, d, y);
y = (y % m[i] + m[i]) % m[i];
x = ((x + multi(multi(a[i], w, M), y, M)) % M + M) % M;
}
return x;
}
2.大质数判定
LL multi(LL a, LL b, LL mod) {
LL ret = 0;
while(b) {
if(b & 1) ret = ret + a;
if(ret >= mod) ret -= mod;
a = a + a;
if(a >= mod) a -= mod;
b >>= 1;
}
return ret;
}
LL power(LL a, LL b, LL mod) {
LL ret = 1;
while(b) {
if(b & 1) ret = multi(ret, a, mod);
a = multi(a, a, mod);
b >>= 1;
}
return ret;
}
bool Miller_Rabin(LL n) {
LL u = n - 1, pre, x;
int i, j, k = 0;
if(n == 2 || n == 3 || n == 5 || n == 7 || n == 11) return true;
if(n == 1 || (!(n % 2)) || (!(n % 3)) || (!(n % 5)) || (!(n % 7)) || (!(n % 11))) return
false;
for(; !(u & 1); k++, u >>= 1);
for(i = 0; i < 5; i++) {
x = rand() % (n - 2) + 2;
x = power(x, u, n);
pre = x;
for(j = 0; j < k; j++) {
x = multi(x, x, n);
if(x == 1 && pre != 1 && pre != (n - 1))
return false;
pre = x;
}
if(x != 1) return false;
}
return true;
}
3.约瑟夫环
/*
F[n] = (F[n - 1] + m) % n, F[1] = 0
返回的下标从0 开始,复杂度大约为O(m)
*/
int Joseph(int n, int m) {
if(n == 1) return 0;
if(m == 1) return n - 1;
LL pre = 0; int now = 2;
while(now <= n) {
if(pre + m >= now) {
pre = (pre + m) % now;
now++;
} else {
int a = now - 1 - pre, b = m - 1;
int k = a / b + (a % b != 0);
if(now + k > n + 1) k = n + 1 - now;
pre = (pre + (LL)m * k) % (now + k - 1);
now += k;
}
}
return pre;
}
博弈
1.Bash博弈
题意:一堆石头共n个,两人轮流取,最少取一个,最多取m个。最后取完的人赢
思路:当m % (n + 1) != 0时先手赢,否则后手赢
2.威佐夫博弈
题意:两堆石子数量分别为a,b。两人轮流取石子,取石子时可以1.从一堆中取任意个
数 2.在两堆中取相同个数。最后取完的人获胜。
思路:满足黄金分割
if(a >= b) swap(a, b);
int k = b - a;
int x = (sqrt(5.0) + 1) / 2 * k, y = x + k;
if(a == x && b == y) printf("B\n");
else printf("A\n");
3.Nim博弈
题意:有n堆石子。A B两个人轮流拿,A先拿。每次只能从一堆中取若干个,可将一堆
全取走,但不可不取,拿到最后1颗石子的人获胜。
思路:Nim博弈相当于把独立游戏分开计算SG函数,然后再用位异或
SG[u] = Mex({后继的集合})相当于取出最小的集合中不存在的数字,可以发现mex的值
总是比后继的个数要少,而且vis数组通常都是开在函数内部,不开在全局变量中,防
止冲突
4.斐波那契博弈
题意:1堆石子n个,第一个人可以取任意个数但不能全部取完,以后每次拿的个数不能
超过上一次对手拿的个数的2倍,轮流拿石子。
思路:后手赢得情况的数字会呈现斐波那契数列
5.Anti-num 博弈
SG函数的求法一模一样最后如果只有一堆也能用SJ定理
如果为Anti-Nim 游戏,如下情况先手胜
SG异或和为0,且单个游戏的SG全部<=1
SG 异或不为0,且存在单个游戏的 SG>1,即<=1的个数不等于独立游戏个数
6.SG函数
/**
* 求出SG函数
* F(存储可以走的步数,Array[0]表示可以有多少种走法)
* F[]需要从小到大排序
* 1.可选步数为1-m的连续整数,直接取模即可,SG(x)=x%(m+1);
* 2.可选步数为任意步,SG(x) = x;
* 3.可选步数为一系列不连续的数,用GetSG(计算)
**/
int SG[MAX], Hash[MAX];
void init(int F[], int n) {
int i, j;
memset(SG, 0, sizeof(SG));
for(i = 0; i <= n; i++) {
memset(Hash, 0, sizeof(Hash));
for(j = 1; j <= F[0]; j++) {
if(i < F[j])
break;
Hash[SG[i - F[j]]] = 1;
}
for(j = 0; j <= n; j++) {
if(Hash[j] == 0) {
SG[i] = j;
break;
}
}
}
}
/**
* 获得一个单独的SG值
* k为可走步数,F数组存储可走步数(0~k-1)
*/
int F[101], sg[10001], k;
int getsg(int m) {
int hash[101] = {0};
int i;
for(i = 0; i < k; i++) {
if(m - F[i] < 0)
break;
if(sg[m - F[i]] == -1)
sg[m - F[i]] = getsg(m - F[i]);
hash[sg[m - F[i]]] = 1;
}
for(i = 0;; i++)
if(hash[i] == 0)
return i;
}
其他
1.祖传头文件
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <vector>
#include <iomanip>
#include <math.h>
#include <map>
using namespace std;
#define FIN freopen("input.txt","r",stdin);
#define FOUT freopen("output.txt","w",stdout);
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
typedef long long LL;
typedef pair<int, int> PII;
2.大数
const int MX = 2500;
const int MAXN = 9999;
const int DLEN = 4;
/*已重载>+- % 和print*/
class Big {
public:
int a[MX], len;
Big(const int b = 0) {
int c, d = b;
len = 0;
memset(a, 0, sizeof(a));
while(d > MAXN) {
c = d - (d / (MAXN + 1)) * (MAXN + 1);
d = d / (MAXN + 1);
a[len++] = c;
}
a[len++] = d;
}
Big(const char *s) {
int t, k, index, L, i;
memset(a, 0, sizeof(a));
L = strlen(s);
len = L / DLEN;
if(L % DLEN) len++;
index = 0;
for(i = L - 1; i >= 0; i -= DLEN) {
t = 0;
k = i - DLEN + 1;
if(k < 0) k = 0;
for(int j = k; j <= i; j++) {
t = t * 10 + s[j] - '0';
}
a[index++] = t;
}
}
Big operator/(const int &b)const {
Big ret;
int i, down = 0;
for(int i = len - 1; i >= 0; i--) {
ret.a[i] = (a[i] + down * (MAXN + 1)) / b;
down = a[i] + down * (MAXN + 1) - ret.a[i] * b;
}
ret.len = len;
while(ret.a[ret.len - 1] == 0 && ret.len > 1) ret.len--;
return ret;
}
bool operator>(const Big &T)const {
int ln;
if(len > T.len) return true;
else if(len == T.len) {
ln = len - 1;
while(a[ln] == T.a[ln] && ln >= 0) ln--;
if(ln >= 0 && a[ln] > T.a[ln]) return true;
else return false;
} else return false;
}
Big operator+(const Big &T)const {
Big t(*this);
int i, big;
big = T.len > len ? T.len : len;
for(i = 0; i < big; i++) {
t.a[i] += T.a[i];
if(t.a[i] > MAXN) {
t.a[i + 1]++;
t.a[i] -= MAXN + 1;
}
}
if(t.a[big] != 0) t.len = big + 1;
else t.len = big;
return t;
}
Big operator-(const Big &T)const {
int i, j, big;
bool flag;
Big t1, t2;
if(*this > T) {
t1 = *this;
t2 = T;
flag = 0;
} else {
t1 = T;
t2 = *this;
flag = 1;
}
big = t1.len;
for(i = 0; i < big; i++) {
if(t1.a[i] < t2.a[i]) {
j = i + 1;
while(t1.a[j] == 0) j++;
t1.a[j--]--;
while(j > i) t1.a[j--] += MAXN;
t1.a[i] += MAXN + 1 - t2.a[i];
} else t1.a[i] -= t2.a[i];
}
t1.len = big;
while(t1.a[t1.len - 1] == 0 && t1.len > 1) {
t1.len--;
big--;
}
if(flag) t1.a[big - 1] = 0 - t1.a[big - 1];
return t1;
}
int operator%(const int &b)const {
int i, d = 0;
for(int i = len - 1; i >= 0; i--) {
d = ((d * (MAXN + 1)) % b + a[i]) % b;
}
return d;
}
Big operator*(const Big &T) const {
Big ret;
int i, j, up, temp, temp1;
for(i = 0; i < len; i++) {
up = 0;
for(j = 0; j < T.len; j++) {
temp = a[i] * T.a[j] + ret.a[i + j] + up;
if(temp > MAXN) {
temp1 = temp - temp / (MAXN + 1) * (MAXN + 1);
up = temp / (MAXN + 1);
ret.a[i + j] = temp1;
} else {
up = 0;
ret.a[i + j] = temp;
}
}
if(up != 0) {
ret.a[i + j] = up;
}
}
ret.len = i + j;
while(ret.a[ret.len - 1] == 0 && ret.len > 1) ret.len--;
return ret;
}
void print() {
printf("%d", a[len - 1]);
for(int i = len - 2; i >= 0; i--) printf("%04d", a[i]);
}
};
3.STL
1.vector
构造
vector <int> v;
基本操作
v.begin() //指向迭代器中第一个元素。
v.end() //指向迭代器中末端元素的下一个,指向一个不存在元素。
v.push_back(elem) //在尾部加入一个数据。
v.pop_back() //删除最后一个数据。
v.capacity() //vector可用空间的大小。
v.size() //返回容器中数据个数。
v.empty() //判断容器是否为空。
v.front() //传回第一个数据。
v.back() //传回最后一个数据,不检查这个数据是否存在。
v.at(index) //传回索引idx所指的数据,如果idx越界,抛出out_of_range。
v.clear() //移除容器中所有数据。
v.erase(iterator) //删除pos位置的数据,传回下一个数据的位置。
v.erase(begin,end) //删除[beg,end)区间的数据,传回下一个数据的位置。注意:begin和end为iterator
v.insert(position,elem) //在pos位置插入一个elem拷贝,传回新数据位置。
v.insert(position,n,elem) //在pos位置插入n个elem数据,无返回值。
v.insert(position,begin,end) //在pos位置插入在[beg,end)区间的数据,无返回值。
2.priority_queue
构造
priority_queue<int>que; //采用默认优先级构造队列最大值先出队
priority_queue<int,vector<int>,greater<int> >que3; //最小值先出队
priority_queue<int,vector<int>,less<int> >que4; //最大值先出队
基本操作
que.empty() //判断一个队列是否为空
que.pop() //删除队顶元素
que.push() //加入一个元素
que.size() //返回优先队列中拥有的元素个数
que.top() //返回优先队列的队顶元素
3.map
构造
map<int, string> mp;
基本操作
mp.begin() //返回指向map头部的迭代器
mp.clear() //删除所有元素
mp.count() //返回指定元素出现的次数
mp.empty() //如果map为空则返回true
mp.end() //返回指向map末尾的迭代器
mp.equal_range() //返回特殊条目的迭代器对
mp.erase() //删除一个元素
mp.find() //查找一个元素
mp.get_allocator() //返回map的配置器
mp.insert() //插入元素
mp.key_comp() //返回比较元素key的函数
mp.lower_bound() //返回键值>=给定元素的第一个位置
mp.max_size() //返回可以容纳的最大元素个数
mp.rbegin() //返回一个指向map尾部的逆向迭代器
mp.rend() //返回一个指向map头部的逆向迭代器
mp.size() //返回map中元素的个数
mp.swap() //交换两个map
mp.upper_bound() //返回键值>给定元素的第一个位置
mp.value_comp() //返回比较元素value的函数
4.queue
构造
queue<int> q;
基本操作
q.push(num) //入列,插入队列的末端。
q.empty() //判断队列是否为空
q.size() //获取队列中的元素个数
q.back() //访问队列尾元素
5.stack
构造
stack<int> s;
stack<int> c1(c2); 复制stack
基本操作
s.top() //返回栈顶数据
s.push(num) //在栈顶插入数据
s.pop() //弹出栈顶数据
s.empty() //判断栈是否为空
s.size() //返回栈中数据的个数
6.set
构造
set<int> s;
基本操作
s.insert(num) //把元素插入到集合中,同一元素不会重复插入
s.inset(first,second) //将定位器first到second之间的元素插入到set中,返回值是void
s.erase(6) //删除键值为6的元素
s.find(6) //查找键值为6的元素,如果没找到则返回end()
s.begin() //返回set容器的第一个元素
s.end() //返回set容器的最后一个元素
s.clear() //删除set容器中的所有的元素
s.empty() //判断set容器是否为空
s.max_size() //返回set容器可能包含的元素最大个数
s.size() //返回当前set容器中的元素个数
s.rbegin() //返回的值和end()相同
s.rend() //返回的值和rbegin()相同
s.count() //用来查找set中某个某个键值出现的次数,一般用于判断某一键值是否在set出现过
erase(iterator) //删除定位器iterator指向的值
erase(first,second) //删除定位器first和second之间的值(不包括second)
erase(key_value) //删除键值key_value的值
lower_bound(key_value) //返回第一个大于等于key_value的定位器
upper_bound(key_value) //返回最后一个大于等于key_value的定位器
7.deque
构造
deque<int> c;
deque<int> c1(c2); //复制deque
deque<int> c(n, elem); //创建一个含有n个elem拷贝的deque
基本操作
c.assign(beg, end); //将beg-end区间中的值赋值给c
c.assign(n, elem); //将n个elem的拷贝赋值给c
c.at(idx); //返回idx位置所指数据
c.front(); //返回第一个数据
c.back(); //返回最后一个数据
c.begin(); //返回指向第一个数据的迭代器
c.end(); //返回指向最后一个数据的下一个位置的迭代器
c.rbegin(); //返回逆向队列的第一个数据
c.rend(); //返回指向逆向队列的最后一个数据的下一个位置的迭代器
c.push_back(elem); //在尾部加入一个数据
c.push_front(elem); //在头部插入一个数据
c.insert(pos, elem); //在pos位置插入一个elem拷贝,返回新数据位置
c.insert(pos, n, elem); //在pos位置插入n个elem数据,无返回值
c.insert(pos, beg, end); //在pos位置插入在beg-end区间的数据,无返回值
c.pop_back(); //删除最后一个数据
c.pop_front(); //删除头部数据
c.erase(pos); //删除pos位置的数据,返回下一个数据的位置
c.erase(beg, end); //删除beg-end区间的数据,返回下一个数据的位置
c.empty(); //判断容器是否为空
c.max_size(); //返回容器中最大数据的数量
c.resize(num); //重新指定队列的长度
c.size(); //返回容器中实际数据的个数
c1.swap(c2) //将c1和c2元素互换
4.正常的读入挂
inline int read() {
int ret = 0, c, f = 1;
for(c = getchar(); !(isdigit(c) || c == '-'); c = getchar());
if(c == '-') f = -1, c = getchar();
for(; isdigit(c); c = getchar()) ret = ret * 10 + c - '0';
if(f < 0) ret = -ret;
return ret;
}
5.fread读入挂
namespace IO {
const int MX = 1e7; //1e7 占用内存11000kb
char buf[MX]; int c, sz;
void begin() {
c = 0;
sz = fread(buf, 1, MX, stdin);
}
inline bool read(int &t) {
while(c < sz && buf[c] != '-' && (buf[c] < '0' || buf[c] > '9')) c++;
if(c >= sz) return false;
bool flag = 0;
if(buf[c] == '-') flag = 1, c++;
for(t = 0; c < sz && '0' <= buf[c] && buf[c] <= '9'; c++) t = t * 10 + buf[c] - '0';
if(flag) t = -t;
return true;
}
}
6.莫队算法
莫队算法,可以解决一类静态,离线区间查询问题。
BZOJ 2038: [2009 国家集训队]小Z 的袜子(hose)
Description
作为一个生活散漫的人,小Z 每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。
终于有一天,小Z 再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,
小Z 把这N 只袜子从1 到N 编号,然后从编号L 到R(L
Input
输入文件第一行包含两个正整数N 和M。N 为袜子的数量,M 为小Z 所提的询问的数量。
接下来一行包含N 个正整数Ci,其中Ci 表示第i 只袜子的颜色,相同的颜色用相同的数字
表示。再接下来M 行,每行两个正整数L,R 表示一个询问。
Output
包含M 行,对于每个询问在一行中输出分数A / B 表示从该询问的区间[L, R]中随机抽出两只
袜子颜色相同的概率。若该概率为0 则输出0 / 1,否则输出的A / B 必须为最简分数。(详见
样例)
Sample Input
6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
Sample Output
2 / 5
0 / 1
1 / 1
4 / 15
只需要统计区间内各个数出现次数的平方和
莫队算法,两种方法,一种是直接分成sqrt(n)块,分块排序。
另外一种是求得曼哈顿距离最小生成树,根据manhattan MST 的dfs 序求解。
1 分块
const int MAXN = 50010;
const int MAXM = 50010;
struct Query {
int L, R, id;
} node[MAXM];
long long gcd(long long a, long long b) {
if(b == 0)return a;
return gcd(b, a % b);
}
struct Ans {
long long a, b; //分数a/b
void reduce() { //分数化简
long long d = gcd(a, b);
a /= d; b /= d;
}
} ans[MAXM];
int a[MAXN];
int num[MAXN];
int n, m, unit;
bool cmp(Query a, Query b) {
if(a.L / unit != b.L / unit)return a.L / unit < b.L / unit;
else return a.R < b.R;
}
void work() {
long long temp = 0;
memset(num, 0, sizeof(num));
int L = 1;
int R = 0;
for(int i = 0; i < m; i++) {
while(R < node[i].R) {
R++;
temp -= (long long)num[a[R]] * num[a[R]];
num[a[R]]++;
temp += (long long)num[a[R]] * num[a[R]];
}
while(R > node[i].R) {
temp -= (long long)num[a[R]] * num[a[R]];
num[a[R]]--;
temp += (long long)num[a[R]] * num[a[R]];
R--;
}
while(L < node[i].L) {
temp -= (long long)num[a[L]] * num[a[L]];
num[a[L]]--;
temp += (long long)num[a[L]] * num[a[L]];
L++;
}
while(L > node[i].L) {
L--;
temp -= (long long)num[a[L]] * num[a[L]];
num[a[L]]++;
temp += (long long)num[a[L]] * num[a[L]];
}
ans[node[i].id].a = temp - (R - L + 1);
ans[node[i].id].b = (long long)(R - L + 1) * (R - L);
ans[node[i].id].reduce();
}
}
int main() {
//FIN
while(~scanf("%d%d", &n, &m)) {
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 0; i < m; i++) {
node[i].id = i;
scanf("%d%d", &node[i].L, &node[i].R);
}
unit = sqrt(n);
sort(node , node + m, cmp);
work();
for(int i = 0; i < m; i++) printf("%lld/%lld\n", ans[i].a, ans[i].b);
}
return 0;
}
2 Manhattan MST的dfs顺序求解
const int MAXN = 50010;
const int MAXM = 50010;
const int INF = 0x3f3f3f3f;
struct Point {
int x, y, id;
} p[MAXN], pp[MAXN];
bool cmp(Point a, Point b) {
if(a.x != b.x) return a.x < b.x;
else return a.y < b.y;
}
//树状数组,找y-x大于当前的,但是y+x最小的
struct BIT {
int min_val, pos;
void init() {
min_val = INF;
pos = -1;
}
} bit[MAXN];
struct Edge {
int u, v, d;
} edge[MAXN << 2];
bool cmpedge(Edge a, Edge b) {
return a.d < b.d;
}
int tot;
int n;
int F[MAXN];
int find(int x) {
if(F[x] == -1) return x;
else return F[x] = find(F[x]);
}
void addedge(int u, int v, int d) {
edge[tot].u = u;
edge[tot].v = v;
edge[tot++].d = d;
}
struct Graph {
int to, next;
} e[MAXN << 1];
int total, head[MAXN];
void _addedge(int u, int v) {
e[total].to = v;
e[total].next = head[u];
head[u] = total++;
}
int lowbit(int x) {
return x & (-x);
}
void update(int i, int val, int pos) {
while(i > 0) {
if(val < bit[i].min_val) {
bit[i].min_val = val;
bit[i].pos = pos;
}
i -= lowbit(i);
}
}
int ask(int i, int m) {
int min_val = INF, pos = -1;
while(i <= m) {
if(bit[i].min_val < min_val) {
min_val = bit[i].min_val;
pos = bit[i].pos;
}
i += lowbit(i);
}
return pos;
}
int dist(Point a, Point b) {
return abs(a.x - b.x) + abs(a.y - b.y);
}
void Manhattan_minimum_spanning_tree(int n, Point p[]) {
int a[MAXN], b[MAXN];
tot = 0;
for(int dir = 0; dir < 4; dir++) {
if(dir == 1 || dir == 3) {
for(int i = 0; i < n; i++)
swap(p[i].x, p[i].y);
} else if(dir == 2) {
for(int i = 0; i < n; i++)
p[i].x = -p[i].x;
}
sort(p, p + n, cmp);
for(int i = 0; i < n; i++)
a[i] = b[i] = p[i].y - p[i].x;
sort(b, b + n);
int m = unique(b, b + n) - b;
for(int i = 1; i <= m; i++)
bit[i].init();
for(int i = n - 1; i >= 0; i--) {
int pos = lower_bound(b, b + m, a[i]) - b + 1;
int ans = ask(pos, m);
if(ans != -1)
addedge(p[i].id, p[ans].id, dist(p[i], p[ans]));
update(pos, p[i].x + p[i].y, i);
}
}
memset(F, -1, sizeof(F));
sort(edge, edge + tot, cmpedge);
total = 0;
memset(head, -1, sizeof(head));
for(int i = 0; i < tot; i++) {
int u = edge[i].u, v = edge[i].v;
int t1 = find(u), t2 = find(v);
if(t1 != t2) {
F[t1] = t2;
_addedge(u, v);
_addedge(v, u);
}
}
}
int m;
int a[MAXN];
struct Ans {
long long a, b;
} ans[MAXM];
long long temp ;
int num[MAXN];
void add(int l, int r) {
for(int i = l; i <= r; i++) {
temp -= (long long)num[a[i]] * num[a[i]];
num[a[i]]++;
temp += (long long)num[a[i]] * num[a[i]];
}
}
void del(int l, int r) {
for(int i = l; i <= r; i++) {
temp -= (long long)num[a[i]] * num[a[i]];
num[a[i]]--;
temp += (long long)num[a[i]] * num[a[i]];
}
}
void dfs(int l1, int r1, int l2, int r2, int idx, int pre) {
if(l2 < l1) add(l2, l1 - 1);
if(r2 > r1) add(r1 + 1, r2);
if(l2 > l1) del(l1, l2 - 1);
if(r2 < r1) del(r2 + 1, r1);
ans[pp[idx].id].a = temp - (r2 - l2 + 1);
ans[pp[idx].id].b = (long long)(r2 - l2 + 1) * (r2 - l2);
for(int i = head[idx]; i != -1; i = e[i].next) {
int v = e[i].to;
if(v == pre) continue;
dfs(l2, r2, pp[v].x, pp[v].y, v, idx);
}
if(l2 < l1)del(l2, l1 - 1);
if(r2 > r1)del(r1 + 1, r2);
if(l2 > l1)add(l1, l2 - 1);
if(r2 < r1)add(r2 + 1, r1);
}
long long gcd(long long a, long long b) {
if(b == 0) return a;
else return gcd(b, a % b);
}
int main() {
while(scanf("%d%d", &n, &m) == 2) {
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 0; i < m; i++) {
scanf("%d%d", &p[i].x, &p[i].y);
p[i].id = i;
pp[i] = p[i];
}
Manhattan_minimum_spanning_tree(m, p);
memset(num, 0, sizeof(num));
temp = 0;
dfs(1, 0, pp[0].x, pp[0].y, 0, -1);
for(int i = 0; i < m; i++) {
long long d = gcd(ans[i].a, ans[i].b);
printf("%lld/%lld\n", ans[i].a / d, ans[i].b / d);
}
}
return 0;
}