洛谷:P1600
OJ : i1600
第一步:LCA+倍增
第二步:不枚举每个运动员,而是枚举每个观察员i,看看哪些结点会为这个观察员i做贡献(刚好在wi秒跑到他这儿)。
第三步:对于一个结点P来说,以P为根递归整颗子树过程中在桶内产生的差值才是有效的
变量含义:
数据变量:SIZE
:定义了最大节点数和玩家数,设为300,000。n
:节点数。m
:玩家数。tot
:边的计数器,用于构建邻接表。h[SIZE]
:邻接表的头指针数组,h[x]
表示节点 x
的第一条边在数组 E
中的索引。deep[SIZE]
:记录每个节点的深度。fa[SIZE][20]
:倍增表,fa[x][i]
表示节点 x
的 2i级祖先。w[SIZE]
:每个节点的观察时间 Wj。
边的结构体变量:edge
:结构体,用于表示树的边,包括目标节点 to
和下一条边的索引 next
。E[SIZE*2]
:主树的边数组。e1[SIZE*2]
:用于存储玩家到终点节点的列表。e2[SIZE*2]
:用于存储玩家到LCA节点的列表。
玩家相关变量:tot1
、tot2
:分别用于统计玩家加入终点节点和LCA节点列表的边数。h1[SIZE]
、h2[SIZE]
:分别表示终点节点和LCA节点的玩家列表头指针。b1[SIZE*2]
、b2[SIZE*2]
:辅助计数数组,用于在第二次DFS中统计玩家位置。js[SIZE]
:记录每个节点作为玩家起点的玩家数量。dist[SIZE]
:记录每个玩家的路径长度。s[SIZE]
、t_nodes[SIZE]
:分别记录每个玩家的起点和终点节点。l[SIZE]
:暂时未使用的数组,可能是冗余的。ans[SIZE]
:记录每个节点被观察到的玩家数量。
add(x, y)
函数将边 (x, y)
添加到邻接表中。由于树是无向的,需双向添加边。dfs1
):
deep[x]
。fa[x][i]
,用于快速计算LCA。fa[x][i]
表示节点 x
的 2i级祖先。s[i]
和终点 t_nodes[i]
。lca = get_lca(s[i], t_nodes[i])
。dist[i] = deep[s[i]] + deep[t_nodes[i]] - 2 * deep[lca]
。js[s[i]]++
统计每个节点作为玩家起点的玩家数量。add1(t_nodes[i], i)
将玩家编号 i
添加到终点节点 t_nodes[i]
的玩家列表中。add2(lca, i)
将玩家编号 i
添加到LCA节点 lca
的玩家列表中。deep[lca] + Wj[lca] == deep[s[i]]
,则 ans[lca]--
。这意味着在节点 lca
处,有一个玩家在观察时间正好位于该节点,需调整计数。b1[d]
:记录当前子树中起点深度为 d
的玩家数量。b2[d]
:记录当前子树中路径长度为 d
的玩家数量。dfs2
):
x
,在进入时保存 b1
和 b2
在特定索引处的值(t1
和 t2
)。dfs2(y)
进行深度优先搜索。b1
和 b2
:
b1[deep[x]] += js[x]
:增加当前节点深度处的玩家数量。h1[x]
,对于每个玩家 y
,更新 b2
。ans[x]
:
ans[x] += b1[w[x] + deep[x]] - t1 + b2[w[x] - deep[x] + SIZE] - t2
。Wj[x]
时刻,有多少玩家位于节点 x
上。b1
和 b2
的状态:
h2[x]
,对于每个玩家 y
,减少相应的 b1
和 b2
计数,以避免影响其他分支。ans[i]
。fa[x][i]
,可以在 O(logN) 时间内计算任意两个节点的LCA。S_i
出发,按照树的边逐步移动,每秒移动一条边,最终到达终点 T_i
。Wj[x]
来确定。Wj[x]
:
x
在时间 Wj[x]
需要统计有多少玩家恰好位于该节点上。js[x]
统计每个节点 x
作为起点的玩家数量。e1
数组将玩家按终点节点分组,便于在第二次DFS中快速访问。e2
数组将玩家按LCA节点分组,便于在第二次DFS中进行特殊处理。b1
和 b2
:
b1[d]
:当前子树中,起点深度为 d
的玩家数量。b2[d]
:当前子树中,路径长度为 d
的玩家数量。x
,在时间 Wj[x]
时刻,位于节点 x
的玩家数量可以通过以下两部分统计:
w[x] + deep[x]
,即玩家在 w[x]
秒后到达节点 x
。w[x] - deep[x]
,即玩家在移动 w[x] - deep[x]
秒后到达节点 x
。ans[x] += b1[w[x] + deep[x]] - t1 + b2[w[x] - deep[x] + SIZE] - t2
,统计满足上述条件的玩家数量。b1
和 b2
的状态,确保其他分支的统计不受影响。这段代码通过两次深度优先搜索(DFS)和二进制提升法(Binary Lifting)有效地计算了每个节点在特定时间 Wj 时刻被观察到的玩家数量。主要步骤包括:
b1
和 b2
,统计满足观察条件的玩家数量。代码实现:
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 | /**************************************************************** * Description: 2016年提高组第二题 天天爱跑步 * Author: Alex Li * Date: 2024-09-30 23:27:41 * LastEditTime: 2024-10-01 10:44:15 ****************************************************************/ #include<iostream> #include<cmath> using namespace std; const int SIZE=300000; // 定义常量,假设节点数和玩家数不超过300000 int n, m, tot, h[SIZE], deep[SIZE], fa[SIZE][20], w[SIZE]; // 定义边的结构体,包含目标节点和下一条边的索引 struct edge{ int to, next; }E[SIZE*2], e1[SIZE*2], e2[SIZE*2]; // 函数:添加边到邻接表 void add(int x, int y){ E[++tot].to = y; // 设置目标节点 E[tot].next = h[x]; // 指向当前节点x的第一条边 h[x] = tot; // 更新节点x的第一条边索引 } int tot1, tot2, h1[SIZE], h2[SIZE]; // 函数:添加玩家到t节点的列表 void add1(int x, int y) { e1[++tot1].to = y; // y表示玩家编号 e1[tot1].next = h1[x]; // 链接当前节点x的玩家列表 h1[x] = tot1; // 更新节点x的玩家列表头 } // 函数:添加玩家到LCA节点的列表 void add2(int x, int y){ e2[++tot2].to = y; // y表示玩家编号 e2[tot2].next = h2[x]; // 链接当前节点x的玩家列表 h2[x] = tot2; // 更新节点x的玩家列表头 } // 深度优先搜索,预处理深度和祖先节点 void dfs1(int x){ // 预处理二进制提升表 for(int i=1; (1<<i)<=deep[x]; i++) fa[x][i] = fa[fa[x][i-1]][i-1]; // 遍历所有邻接节点 for(int i=h[x]; i; i=E[i].next){ int y = E[i].to; if(y == fa[x][0]) continue; // 避免回到父节点 fa[y][0] = x; // 设置y的直接父节点为x deep[y] = deep[x] + 1; // 设置y的深度 dfs1(y); // 递归DFS } } // 计算两个节点的最近公共祖先(LCA) int get_lca(int x, int y){ if(x == y) return x; // 如果两个节点相同,直接返回 if(deep[x] < deep[y]) swap(x, y); // 确保x的深度不小于y // 将x提升到与y相同的深度 int t = log(deep[x] - deep[y]) / log(2); for(int i=t; i>=0; i--){ if(deep[fa[x][i]] >= deep[y]) x = fa[x][i]; if(x == y) return x; } // 同时提升x和y,找到LCA t = log(deep[x]) / log(2); for(int i=t; i>=0; i--){ if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; } return fa[x][0]; } int b1[SIZE*2], b2[SIZE*2], js[SIZE], dist[SIZE], s[SIZE], t_nodes[SIZE], l[SIZE], ans[SIZE]; // 第二次DFS,用于计算每个节点被观察到的玩家数量 void dfs2(int x){ // 保存当前b1和b2的状态 int t1 = b1[w[x] + deep[x]]; int t2 = b2[w[x] - deep[x] + SIZE]; // 遍历所有子节点 for(int i=h[x]; i; i=E[i].next){ int y = E[i].to; if(y == fa[x][0]) continue; // 避免回到父节点 dfs2(y); // 递归DFS } // 更新b1和b2 b1[deep[x]] += js[x]; for(int i=h1[x]; i; i=e1[i].next){ int y = e1[i].to; b2[dist[y] - deep[t_nodes[y]] + SIZE]++; } // 计算当前节点的答案 ans[x] += b1[w[x] + deep[x]] - t1 + b2[w[x] - deep[x] + SIZE] - t2; // 恢复b1和b2的状态,避免影响其他分支 for(int i=h2[x]; i; i=e2[i].next){ int y = e2[i].to; b1[deep[s[y]]]--; b2[dist[y] - deep[t_nodes[y]] + SIZE]--; } } int main(){ freopen("running.in","r",stdin); freopen("running.out","w",stdout); // 读取节点数n和玩家数m cin>>n>>m; // 读取n-1条边,构建树的邻接表 for(int i=1; i<n; i++){ int u, v; cin>>u>>v; add(u, v); add(v, u); } // 初始化根节点的深度和父节点 deep[1] = 1; fa[1][0] = 1; dfs1(1); // 预处理深度和二进制提升表 // 读取每个节点的观察时间Wj for(int i=1; i<=n; i++) cin>>w[i]; // 读取m个玩家的信息 for(int i=1; i<=m; i++){ cin>>s[i]>>t_nodes[i]; int lca = get_lca(s[i], t_nodes[i]); // 计算玩家的LCA dist[i] = deep[s[i]] + deep[t_nodes[i]] - 2 * deep[lca]; // 计算玩家的路径长度 js[s[i]]++; // 统计每个起点有多少玩家 add1(t_nodes[i], i); // 将玩家加入终点节点的列表 add2(lca, i); // 将玩家加入LCA节点的列表 // 如果LCA节点的深度加上Wj恰好等于起点的深度,减去该节点的答案 if(deep[lca] + w[lca] == deep[s[i]]) ans[lca]--; } dfs2(1); // 计算每个节点被观察到的玩家数量 // 输出每个节点的答案 for(int i=1; i<=n; i++) cout<<ans[i]<<' '; //printf("%d ", ans[i]); return 0; } |