题意:给定一个树形图,节点10^5,有两种操作,一种是把某两点间路径(路径必定唯一)上所有点的权值增加一个固定值。
另一种也是相同操作,不同的是给边加权值。操作次数10^5。求操作过后,每个点和每条边的权值。
分析:此题时间卡得非常紧,最好用输入外挂,最好不要用RMQ来求解LCA。
此题是典型的在线LCA问题,先讲讲在线LCA要怎么做。
在线LCA有两种方法,第一种比较常见,即将其转化成RMQ问题。
先对树形图进行深度优先遍历,遍历过程记录路线中点的途经序列,每个非叶子节点会在序列中出现多次,从一个节点A的一个子节点回到A点再走另一个子节点的时候要再次加A加入序列。
记录序列的同时还要记录序列中每个点在树中对应的深度。以及在序列中第一次出现的位置(其实不一定非要第一个才行),主要用于根据点标号查找其在序列中对应的下标。
此时,LCA已经转化为RMQ,如果要求a,b的LCA,只需要找到a,b在遍历序列中分别对应的位置,并在深度序列中查找以这两点为端点的区间内的最小值即可。这个最小值在遍历序列中对应的点就是他们的LCA。
这种方法预处理O(NlogN),查询是O(1)。
模板如下:
//first call init_LCA(root). //then call LCA(a, b) to quest the LCA of a and b. //the graph can be both bidirected or unidirected. #define MAX_NODE_NUM 0 #define MAX_EDGE_NUM 0 #define M 30 struct Edge { int v, next, id; Edge() {} Edge(int v, int next, int id):v(v), next(next), id(id) {} } edge[MAX_EDGE_NUM]; int head[MAX_NODE_NUM]; int edge_cnt; void init_edge() { memset(head, -1, sizeof(head)); edge_cnt = 0; } void add_edge(int u, int v, int id) { edge[edge_cnt] = Edge(v, head[u], id); head[u] = edge_cnt++; } bool vis[MAX_NODE_NUM]; int father[MAX_NODE_NUM]; int power[M]; int st[MAX_NODE_NUM * 2][M]; int ln[MAX_NODE_NUM * 2]; int seq_cnt; int seq[2*MAX_NODE_NUM]; int depth[2*MAX_NODE_NUM]; int first_appearance[MAX_NODE_NUM]; //returns the index of the first minimum value in [x, y] void init_RMQ(int f[], int n) { int i, j; for (power[0] = 1, i = 1; i < 21; i++) { power[i] = 2 * power[i - 1]; } for (i = 0; i < n; i++) { st[i][0] = i; } ln[0] = -1; for (int i = 1; i <= n; i++) { ln[i] = ln[i >> 1] + 1; } for (j = 1; j < ln[n]; j++) { for (i = 0; i < n; i++) { if (i + power[j - 1] - 1 >= n) { break; } //for maximum, change ">" to "<" //for the last, change "<" or ">" to "<=" or ">=" if (f[st[i][j - 1]] > f[st[i + power[j - 1]][j - 1]]) { st[i][j] = st[i + power[j - 1]][j - 1]; } else { st[i][j] = st[i][j - 1]; } } } } int query(int x, int y) { if(x > y) { swap(x, y); } int k = ln[y - x + 1]; //for maximum, change ">" to "<" //for the last, change "<" or ">" to "<=" or ">=" if (depth[st[x][k]] > depth[st[y - power[k] + 1][k]]) return st[y - power[k] + 1][k]; return st[x][k]; } void dfs(int u ,int current_depth) { vis[u] = true; first_appearance[u] = seq_cnt; depth[seq_cnt] = current_depth; seq[seq_cnt++] = u; for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if (vis[v]) { continue; } father[v] = u; if (!vis[v]) { dfs(v, current_depth + 1); depth[seq_cnt] = current_depth; seq[seq_cnt++] = u; } } } void init_LCA(int root) { memset(vis, 0, sizeof(vis)); father[root] = -1; seq_cnt = 0; dfs(root, 0); init_RMQ(depth, seq_cnt); } //O(1) int LCA(int u ,int v) { int x = first_appearance[u]; int y = first_appearance[v]; int res = query(x, y); return seq[res]; }
另一种方法用到了DP的思想。
用一个数组f[i][j]表示i点在树中到根节点的序列中距离i边数为2^j的点。
那么f[i][j] = f[ f[i][j - 1] ][j - 1]。
具体做法是,我们进行BFS,记录每个点的父节点,即f[i][0]。和每个点的深度。
然后根据状态转移公式填充整个数组。
在查询时,先看a,b两点谁的深度大,利用两者深度差的二进制序列,配合f数组,找到较深的点在较浅的点那层的祖先。
然后继续使用f数组,每次向上探测2^i的距离的点两者的祖先是否为同一个,如果不是则i++后继续叠加向上探测2^i,如果是同一个则i--后重新探测。直到找到最小的公共祖先为止。
这种方法预处理O(NlogN),查询是O(NlogN)。但与上一种方法相比,不需要dfs,而用bfs,这样可以节省很多时间。
模板如下:
#define MAX_NODE_NUM 0 #define MAX_EDGE_NUM 0 #define MAX_Q_LEN MAX_NODE_NUM #define M 30 struct Edge { int v, next, id; Edge() {} Edge(int v, int next, int id):v(v), next(next), id(id) {} } edge[MAX_EDGE_NUM]; int head[MAX_NODE_NUM]; int edge_cnt; void init_edge() { memset(head, -1, sizeof(head)); edge_cnt = 0; } void add_edge(int u, int v, int id) { edge[edge_cnt] = Edge(v, head[u], id); head[u] = edge_cnt++; } bool vis[MAX_NODE_NUM]; int father[MAX_NODE_NUM][M]; int depth[MAX_NODE_NUM]; template<typename T> class queue { T data[MAX_Q_LEN]; int head, rear; public: queue() { head = rear = 0; } bool empty() { return head == rear; } void pop() { head++; if (head >= MAX_Q_LEN) head = 0; } void push(T a) { data[rear++] = a; if (rear >= MAX_Q_LEN) rear = 0; } T front() { return data[head]; } }; void bfs(int root) { queue<int> q; q.push(root); seq2_cnt = 0; while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = true; seq2[seq2_cnt++] = u; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if (vis[v]) { continue; } father[v][0] = u; depth[v] = depth[u] + 1; q.push(v); } } } //index start from 1. void init_LCA(int root) { fill_n(vis, node_num + 1, 0); memset(father, 0, sizeof(father)); bfs(root); bool did; for (int i = 1; i < M; i++) { did = false; for (int j = 1; j <= node_num; j++) { int k = father[j][i - 1]; if (k <= 0) { continue; } father[j][i] = father[k][i - 1]; did = true; } if (!did) { break; } } } //O(log(n)) int LCA(int x, int y) { if (depth[x] > depth[y]) { swap(x, y); } int diff = depth[y] - depth[x]; for (int i = 0; i < M && diff; i++) { if (diff & 1) { y = father[y][i]; } diff >>= 1; } if (x == y) { return x; } int exp = 0; while (x != y) { if (!exp || father[x][exp] != father[y][exp]) { x = father[x][exp]; y = father[y][exp]; exp++; }else { exp--; } } return x; }
再说说这题是怎么做的。將种操作进行一下转化,认为每次加权操作都是分别向由两点到他们的LCA的路径加权。
还可以进行进一步的转化设a,b为要加权的路径两端点,他们的LCA为c,根节点为root。
那么该加权操作可转化为由a和b到root分别加权,再由c到root加两倍的负权。这样正负抵消后与原操作等价。
这样转化之后,所有的操作都便成了到根节点的操作,那么只需要將所有的操作标记在非根节点的另一个点上,然后自底向上把操作树中的每个点,將该点的子节点中的权值操作向上传递即可。
本题不可以使用第一种方法,会超时,可能是dfs太耗时。用第二种方法虽然查询时间稍慢,但是通过了。
代码如下:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <cmath> using namespace std; #define MAX_NODE_NUM 100005 #define MAX_EDGE_NUM MAX_NODE_NUM * 2 #define MAX_Q_LEN MAX_NODE_NUM #define M 30 #define D(x) struct Edge { int v, next, id; Edge() {} Edge(int v, int next, int id):v(v), next(next), id(id) {} } edge[MAX_EDGE_NUM]; int head[MAX_NODE_NUM]; int edge_cnt; void init_edge() { memset(head, -1, sizeof(head)); edge_cnt = 0; } void add_edge(int u, int v, int id) { edge[edge_cnt] = Edge(v, head[u], id); head[u] = edge_cnt++; } int node_num, opr_num; long long edge_opr[MAX_NODE_NUM]; long long node_opr[MAX_NODE_NUM]; bool vis[MAX_NODE_NUM]; long long ans_edge[MAX_EDGE_NUM]; int father[MAX_NODE_NUM][M]; int depth[MAX_NODE_NUM]; int seq2[MAX_NODE_NUM]; int seq2_cnt; template<typename T> class queue { T data[MAX_Q_LEN]; int head, rear; public: queue() { head = rear = 0; } bool empty() { return head == rear; } void pop() { head++; if (head >= MAX_Q_LEN) head = 0; } void push(T a) { data[rear++] = a; if (rear >= MAX_Q_LEN) rear = 0; } T front() { return data[head]; } }; void bfs(int root) { queue<int> q; q.push(root); seq2_cnt = 0; while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = true; seq2[seq2_cnt++] = u; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if (vis[v]) { continue; } father[v][0] = u; depth[v] = depth[u] + 1; q.push(v); } } } //index start from 1. void init_LCA(int root) { fill_n(vis, node_num + 1, 0); memset(father, 0, sizeof(father)); bfs(root); bool did; for (int i = 1; i < M; i++) { did = false; for (int j = 1; j <= node_num; j++) { int k = father[j][i - 1]; if (k <= 0) { continue; } father[j][i] = father[k][i - 1]; did = true; } if (!did) { break; } } } int LCA(int x, int y) { if (depth[x] > depth[y]) { swap(x, y); } int diff = depth[y] - depth[x]; for (int i = 0; i < M && diff; i++) { if (diff & 1) { y = father[y][i]; } diff >>= 1; } if (x == y) { return x; } int exp = 0; while (x != y) { if (!exp || father[x][exp] != father[y][exp]) { x = father[x][exp]; y = father[y][exp]; exp++; }else { exp--; } } return x; } inline int read_int() { int num = 0; int sign = 1; bool skip = false; int c = 0; while((c = getchar()) != EOF) { if(c == '-') { sign = -1; skip = true; } else if(c >= '0' && c <= '9') { num = num * 10 + c - '0'; skip = true; } else if(skip) { break; } } return num * sign; } inline int ReadOP() { int c = 0; while((c = getchar()) != EOF && c != 'A'); getchar(); getchar(); return getchar(); } void input() { scanf("%d%d", &node_num, &opr_num); for (int i = 0; i < node_num - 1; i++) { int a, b; a = read_int(); b = read_int(); add_edge(a, b, i); add_edge(b, a, i); } init_LCA(1); fill_n(edge_opr, node_num + 1, 0); fill_n(node_opr, node_num + 1, 0); fill_n(ans_edge, node_num + 1, 0); for (int i = 0; i < opr_num; i++) { int a, b, k; int op = ReadOP(); a = read_int(); b = read_int(); k = read_int(); int c = LCA(a, b); D(printf("%d\n", c)); if (op == '2') { edge_opr[c] -= k * 2; edge_opr[a] += k; edge_opr[b] += k; }else { node_opr[c] -= k; if (father[c][0] > 0) { node_opr[father[c][0]] -= k; } node_opr[a] += k; node_opr[b] += k; } } } void work() { for (int i = seq2_cnt - 1; i >= 0; i--) { int u = seq2[i]; D(printf("%d %lld\n", u, node_opr[u])); for (int j = head[u]; j != -1; j = edge[j].next) { int v = edge[j].v; if (v == father[u][0]) { continue; } node_opr[u] += node_opr[v]; edge_opr[u] += edge_opr[v]; ans_edge[edge[j].id] = edge_opr[v]; } D(printf("%d %lld\n", u, node_opr[u])); } } void output() { bool first = true; for (int i = 1; i <= node_num; i++) { if (first) { first = false; }else { putchar(' '); } printf("%lld", node_opr[i]); } puts(""); first = true; for (int i = 0; i < node_num - 1; i++) { if (first) { first = false; }else { putchar(' '); } printf("%lld", ans_edge[i]); } puts(""); } int main() { int t; scanf("%d", &t); for (int i = 0; i < t; i++) { printf("Case #%d:\n", i + 1); init_edge(); seq2_cnt = 0; input(); work(); output(); } return 0; }