|
转自: http://www.cppblog.com/Icyflame/
一、定义与定理 LCA:Least Common Ancestors(最近公共祖先),对于一棵有根树T的任意两个节点u,v,求出LCA(T, u, v),即离跟最远的节点x,使得x同时是u和v的祖先。 在线算法:用比较长的时间做预处理,但是等信息充足以后每次回答询问只需要用比较少的时间。 离线算法:先把所有的询问读入,然后一起把所有询问回答完成。 RMQ:给出一个数组A,回答询问RMQ(A, i, j),即A[i...j]之间的最值的下标。 二、DFS+RMQ 1)Sparse Table(ST)算法 描述:
1 //初始化 2 INIT_RMQ 3 //max[i][j]中存的是重j开始的i个数据中的最大值,最小值类似,num中存有数组的值 4 for i : 1 to n 5 max[0][i] = num[i] 6 for i : 1 to log(n)/log(2) 7 for j : 1 to n 8 max[i][j] = MAX(max[i-1][j], max[i-1][j+2^(i-1)] 9 //查询 10 RMQ(i, j) 11 k = log(j-i+1) / log(2) 12 return MAX(max[k][i], max[k][j-2^k+1])
分析:初始化过程实际上是一个动态规划的思想。易知,初始化过程效率是O(nlogn),而查询过程效率是O(1)。ST是一个在线算法。 示例:POJ 3368 解题报告 2)求解LCA问题 描述: (1)DFS:从树T的根开始,进行深度优先遍历,并记录下每次到达的顶点。第一个的结点是root(T),每经过一条边都记录它的端点。由于每条边恰好经过2次,因此一共记录了2n-1个结点,用E[1, ... , 2n-1]来表示。 (2)计算R:用R[i]表示E数组中第一个值为i的元素下标,即如果R[u] < R[v]时,DFS访问的顺序是E[R[u], R[u]+1, ..., R[v]]。虽然其中包含u的后代,但深度最小的还是u与v的公共祖先。 (3)RMQ:当R[u] ≥ R[v]时,LCA[T, u, v] = RMQ(L, R[v], R[u]);否则LCA[T, u, v] = RMQ(L, R[u], R[v]),计算RMQ。 由于RMQ中使用的ST算法是在线算法,所以这个算法也是在线算法。 示例:ZOJ 3195 解题报告。 三、Tarjan算法 描述:Tarjan算法是一个离线算法,也就是说只有先获得所有的查询,再按一个特定的顺序进行运算。
1 //parent为并查集,FIND为并查集的查找操作 2 Tarjan(u) 3 visit[u] = true 4 for each (u, v) in QUERY 5 if visit[v] 6 ans(u, v) = FIND(v) 7 for each (u, v) in TREE 8 if !visit[v] 9 Tarjan(v) 10 parent[v] = u
示例:HDOJ 2586 解题报告。
思路: 每次新增点a -> b的时候,union (b, a),节点中保存的是距离的偏移量。 查询a, b的关系的时候,如果a, b不是一类,就输出-1,如果是的话,就将a,b的偏移量相加然后输出。
#include <stdio.h>
struct node { int a, b; struct node *next; };
struct node nodes[10032];
struct { int from, to, len; char dir; struct node *query; } arr[40032];
struct { int parent, x, y; } set[400032];
int N, M;
__inline int abs(int i) { return i > 0 ? i : -i; }
int set_find(int i) { int r, p;
p = set[i].parent; if (!p) return i; r = set_find(p); set[i].x += set[p].x; set[i].y += set[p].y; set[i].parent = r; return r; }
void set_union(int from, int to, int x, int y) { int r = set_find(to); set[r].parent = from; set[r].x = x - set[to].x; set[r].y = y - set[to].y; }
int main() { int i, j, k, ia, ib, x, y; char str[16]; struct node *t, **pt;
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d%d", &N, &M); for (i = 1; i <= M; i++) { scanf("%d%d%d%s", &arr[i].from, &arr[i].to, &arr[i].len, str); arr[i].dir = str[0]; } scanf("%d", &k); for (i = 0; i < k; i++) { scanf("%d%d%d", &nodes[i].a, &nodes[i].b, &j); for (pt = &arr[j].query; *pt; pt = &(*pt)->next); *pt = &nodes[i]; } for (i = 1; i <= M; i++) { switch (arr[i].dir) { case 'E': x = arr[i].len; y = 0; break; case 'W': x = -arr[i].len; y = 0; break; case 'N': x = 0; y = arr[i].len; break; case 'S': x = 0; y = -arr[i].len; break; } set_union(arr[i].from, arr[i].to, x, y); for (t = arr[i].query; t; t = t->next) { ia = set_find(t->a); ib = set_find(t->b); if (ia != ib) j = -1; else j = abs(set[t->a].x - set[t->b].x) + abs(set[t->a].y - set[t->b].y); printf("%d\n", j); } }
return 0; }
思路: 每个节点记录在它以下的所有孩子的数目。后序遍历就比较容易得出结果了。
#include <stdio.h>
struct node { struct node *next; int idx; }; struct node *map[10032], nodes[10032*2]; int N, nodes_cnt, can[10032];
__inline void add(int a, int b) { struct node *p = &nodes[nodes_cnt++]; p->idx = b; p->next = map[a]; map[a] = p; }
int dfs(int idx, int parent) { struct node *p; int sum, i, res;
sum = 1; res = 1; for (p = map[idx]; p; p = p->next) { if (p->idx == parent) continue; i = dfs(p->idx, idx); if (i > N/2) res = 0; sum += i; } if (N - sum > N/2) res = 0; can[idx] = res; return sum; }
int main() { int i, a, b;
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d", &N); for (i = 0; i < N - 1; i++) { scanf("%d%d", &a, &b);
add(a, b); add(b, a); } dfs(1, 0); for (i = 1; i <= N; i++) if (can[i]) printf("%d\n", i);
return 0; }
思路: 典型的最小生成树,Prim搞定。但是速度好慢,用堆应该好一点。
#include <stdio.h>
int N, M;
struct node { int idx, w; struct node *next; }; struct node edges[40032], *map[1024], *exists[1024][1024]; int edges_cnt; char visited[1024];
__inline void add(int a, int b, int w) { struct node *p;
if (exists[a][b]) { if (exists[a][b]->w < w) exists[a][b]->w = w; return ; } p = &edges[edges_cnt++]; p->idx = b; p->w = w; p->next = map[a]; map[a] = p; exists[a][b] = p; }
int main() { int i, j, k, max_val, max_idx, sum; struct node *p;
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d%d", &N, &M); while (M--) { scanf("%d%d%d", &i, &j, &k); add(i, j, k); add(j, i, k); } visited[1] = 1; sum = 0; for (k = 0; k < N - 1; k++) { max_val = -1; for (i = 1; i <= N; i++) { if (!visited[i]) continue; for (p = map[i]; p; p = p->next) if (!visited[p->idx] && p->w > max_val) { max_val = p->w; max_idx = p->idx; } } if (max_val == -1) break; sum += max_val; visited[max_idx] = 1; }
printf("%d\n", k == N - 1 ? sum : -1);
return 0; }
思路: 按照每个区间[a, b]中的a来从小到大排序。 第一次,计算开头落在 [0, 0] 之间的区间[a, b]中,b的最大值 b1。 第二次,计算开头落在 [0, b1 + 1] 之间的区间[a, b]中,b的最大值 b2。 第三次,计算开头落在 [b1 + 1, b2 + 1] 之间的区间 [a, b] 中,b的最大值 b3。 。。。
#include <stdio.h> #include <stdlib.h>
struct node { int start, end; };
struct node arr[25032]; int N, T;
int cmp(const void *a, const void *b) { return ((struct node *)a)->start - ((struct node *)b)->start; }
int main() { int i, max_val, cnt, end;
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d%d", &N, &T); for (i = 0; i < N; i++) scanf("%d%d", &arr[i].start, &arr[i].end); qsort(arr, N, sizeof(arr[0]), cmp); i = cnt = 0; end = 1; while (end <= T) { max_val = 0; while (i < N && arr[i].start <= end) { if (arr[i].end > max_val) max_val = arr[i].end; i++; } if (!max_val) break; end = max_val + 1; cnt++; } printf("%d\n", end == T + 1 ? cnt : -1); }
思路:
用线段树维护所有线段的分布。 新增加一个fence的时候,将fence的范围[a, b]插入线段树,节点的值为fence的编号,即高度。 那么fence上的某一点就是树的叶子了,从叶子往上一直到根节点的路径中节点的最大值, 就是从fence上的这一点垂直掉下去后,碰到的一个fence了。
这样,我们就可以在O(lgN)时间内知道,从一个fence的端点掉下去会碰到哪个fence了。 不然从后往前一个个找就是O(N)复杂度了。同时N也很大,必然超时。 同时也可以发现,一个fence保存两个值用作动态规划就好了,向左、右走之后,掉到其他fence上面,然后回到基地所用的最短路径。 推的方法很简单,掉到其他fence上面之后,看下是向左走距离短还是向右走距离短。就行了。 这个代码跑到400ms。
#include <stdio.h>
#define MAX_N 50032 #define MAX_R 100032
struct { int a, b; } dp[MAX_N], fences[MAX_N]; int N, S, tree[MAX_R*16];
__inline int max(int a, int b) { return a > b ? a : b; }
__inline int abs(int a) { return a > 0 ? a : -a; }
__inline int min(int a, int b) { return a < b ? a : b; }
void insert(int idx, int start, int end, int left, int right, int val) { int mid;
if (start == left && right == end) { tree[idx] = val; return ; } mid = (start + end) / 2; if (right <= mid) insert(idx*2, start, mid, left, right, val); else if (left > mid) insert(idx*2+1, mid + 1, end, left, right, val); else { insert(idx*2, start, mid, left, mid, val); insert(idx*2+1, mid + 1, end, mid + 1, right, val); } }
int query(int idx, int start, int end, int pos) { int val, mid;
if (start == pos && end == pos) return tree[idx]; mid = (start + end) / 2; if (pos <= mid) val = query(idx*2, start, mid, pos); else val = query(idx*2+1, mid + 1, end, pos); return max(val, tree[idx]); }
__inline int calc_min(int i, int pos) { if (!i) return abs(pos - MAX_R); return min(pos - fences[i].a + dp[i].a, fences[i].b - pos + dp[i].b); }
int main() { int i;
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d%d", &N, &S); S += MAX_R; for (i = 1; i <= N; i++) { scanf("%d%d", &fences[i].a, &fences[i].b); fences[i].a += MAX_R; fences[i].b += MAX_R; dp[i].a = calc_min(query(1, 0, MAX_R*2, fences[i].a), fences[i].a); dp[i].b = calc_min(query(1, 0, MAX_R*2, fences[i].b), fences[i].b); insert(1, 0, MAX_R*2, fences[i].a, fences[i].b, i); } printf( "%d\n", min(S - fences[N].a + dp[N].a, fences[N].b - S + dp[N].b) );
return 0; }
思路: 开一个 64*64*64 大小的数组,记录该位置是否有放置方块。 开一个 25000 大小的数组,记录每个方块的位置。 然后每放一个方块,首先看该位置能不能放,然后再看6个方向是否有其他方块,如果有的话,就要调整总面积的和。
#include <stdio.h>
char placed[64][64][64]; struct node { int x, y, z; } box[25032];
int main() { int i, j, x, y, z, sum, N; char str[16];
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d", &N); box[1].x = 32; box[1].y = 32; box[1].z = 0; placed[32][32][0] = 1; sum = 5; for (i = 2; i <= N; i++) { scanf("%d%s", &j, str); x = box[j].x; y = box[j].y; z = box[j].z; switch (str[0]) { case 'L': x--; break; case 'R': x++; break; case 'F': y--; break; case 'B': y++; break; case 'O': z++; break; case 'U': z--; break; } if (z < 0) break; if (placed[x][y][z]) break; box[i].x = x; box[i].y = y; box[i].z = z; placed[x][y][z] = 1; sum += 6; if (placed[x - 1][y][z]) sum -= 2; if (placed[x + 1][y][z]) sum -= 2; if (placed[x][y - 1][z]) sum -= 2; if (placed[x][y + 1][z]) sum -= 2; if (!z) sum--; else if (placed[x][y][z - 1]) sum -= 2; if (placed[x][y][z + 1]) sum -= 2; }
printf("%d\n", i <= N ? -1 : sum);
return 0; }
思路: 可以把一连串数字看成多个连续的递减序列。 所有递减序列的高度和就是答案了。 最后一个数字特殊处理。
#include <stdio.h>
int main() { int p, i, pre, first, cur, sum;
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d%d", &p, &pre); sum = 0; first = pre; while (--p) { scanf("%d", &cur); if (p == 1) { if (cur < pre) sum += first; else sum += first - pre + cur; } else if (cur < pre) pre = cur; else { sum += first - pre; first = pre = cur; } } printf("%d\n", sum);
return 0; }
也可以用floyd。
#include <stdio.h> #include <string.h>
#define MAX_N 332
char conn[MAX_N][MAX_N], visited[MAX_N]; int N, M, qh, qt, min_val, val; struct { int step, idx; } queue[MAX_N];
__inline void insert(int i, int s) { if (visited[i]) return ; queue[qt].idx = i; queue[qt].step = s; val += s; qt++; visited[i] = 1; }
__inline void bfs(int idx) { int i, step;
memset(visited, 0, N + 1); val = qh = qt = 0; insert(idx, 0); while (qh != qt) { idx = queue[qh].idx; step = queue[qh].step; qh++; for (i = 1; i <= N; i++) if (conn[idx][i]) insert(i, step + 1); } if (val < min_val) min_val = val; }
int main() { int i, j, cnt, arr[MAX_N], a, b;
freopen("e:\\test\\in.txt", "r", stdin);
min_val = 0x7fffffff; scanf("%d%d", &N, &M); while (M--) { scanf("%d", &cnt); for (i = 0; i < cnt; i++) { scanf("%d", &arr[i]); for (j = 0; j < i; j++) { a = arr[i]; b = arr[j]; conn[a][b] = conn[b][a] = 1; } } } for (i = 1; i <= N; i++) bfs(i); printf("%d\n", 100 * min_val / (N - 1));
return 0; }
题目大意: 给出一个N*N的矩阵,要查询任意B*B子矩阵内的元素最大值和最小值之差。 思路: 这应该算是一个二维的 RMQ 问题。但是做之前还不知道有RMQ这回事,就用一个动态规划做了。 还好速度也慢不到哪里去,也过了。哈哈。
#include <stdio.h>
struct node { unsigned char arr[254], max, min; };
__inline void node_init(struct node *n) { n->max = 0; n->min = 255; }
__inline void node_add(struct node *n, unsigned char val) { n->arr[val]++; if (val > n->max) n->max = val; if (val < n->min) n->min = val; }
__inline void node_del(struct node *n, unsigned char val) { n->arr[val]--; while (!n->arr[n->max]) n->max--; while (!n->arr[n->min]) n->min++; }
int N, B, K; unsigned char data[256][256]; struct node row[256], col[256]; unsigned char ans[256][256];
int main() { int i, j, k;
freopen("e:\\test\\in.txt", "r", stdin);
scanf("%d%d%d", &N, &B, &K); for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { scanf("%d", &k); data[i][j] = k; } }
for (i = 0; i < N; i++) { node_init(&row[i]); for (j = 0; j < B; j++) node_add(&row[i], data[i][j]); } for (i = 0; ; i++) { node_init(&col[i]); for (j = 0; j < B; j++) { node_add(&col[i], row[j].max); node_add(&col[i], row[j].min); } while (1) { ans[j - B][i] = col[i].max - col[i].min; if (j == N) break; node_del(&col[i], row[j - B].max); node_del(&col[i], row[j - B].min); node_add(&col[i], row[j].max); node_add(&col[i], row[j].min); j++; } if (i == N - B) break; for (j = 0; j < N; j++) { node_del(&row[j], data[j][i]); node_add(&row[j], data[j][i + B]); } }
while (K--) { scanf("%d%d", &i, &j); printf("%d\n", ans[i - 1][j - 1]); }
return 0; }
|