luogu1600 天天爱跑步
题目大意
游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。现在有个玩家,第个玩家的起点为Si ,终点为Ti 。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J 。 小C想知道每个观察员会观察到多少人?$n,m\leq 300000$
题解
凡是跟树有关的查询等问题都要将无根树化为有根树,而此题中我们可以把所有的路径$(s,t)$分为两个部分:$s\rightarrow \mathrm{lca}(s,t), \mathrm{lca}(s,t)\rightarrow t$。我们先统计每个点观察到的上升玩家的个数,再统计每个点观察到的下降玩家的个数。如果玩家(s,t)在上升的时候遇到了点cur,则cur->Depth + cur->W == s->Depth(1);如果是在下降的时候遇到的点cur,则cur->W - cur->Depth == dist(s,t) - t->Depth(2)。而(s,t)在上升时遇到cur当且仅当在Dfs时lca(s,t)仍然在搜索栈内。因此我们可以维护一个桶,分别维护当lca(s,t)在搜索栈中时,(1)和(2)中满足等号右边的值的路径的个数。在回溯时1.更新当前节点Ans其观察到的玩家的数量为搜索子树后桶内的值减去搜索字数前桶内的值(也就是子树内的值)。2.将以当前节点为lca的路径在桶内的贡献减去。统计上升节点时,先1后2;统计下降节点时,先2后1,以防止lca算重的情况。
注意求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 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 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | #include <cstdio> #include <cstring> #include <algorithm> #include <cassert> using namespace std; const int MAX_NODE = 300010, MAX_EDGE = MAX_NODE * 2, MAX_PATH = 300010, MAX_LINK = MAX_PATH * 4, MAX_W = MAX_NODE; int Bucket[MAX_W * 2]; struct Node; struct Edge; struct Link; struct Path; struct Node { Edge *Head; Link *AsLcaHead, *AsTHead, *TarjanHead; Node *Father, *UnFa; int Depth, W, DfsN, AsSCnt, Ans; }_nodes[MAX_NODE], *Root; int TotNode; struct Edge { Node *To; Edge *Next; }_edges[MAX_EDGE]; int _eCount; struct Link { Path *In; Link *Next; }_links[MAX_LINK]; int _linkCnt; struct Path { Node *Start, *Target, *Lca; int Dist; }_paths[MAX_PATH]; int TotPath; void AddEdge(Node *from, Node *to) { Edge *e = _edges + ++_eCount; e->To = to; e->Next = from->Head; from->Head = e; } void AddLink(Link*&head, Path *in) { Link *link = _links + ++_linkCnt; link->In = in; link->Next = head; head = link; } Node *GetRoot(Node *cur) { return cur->UnFa == cur ? cur : cur->UnFa = GetRoot(cur->UnFa); } void Tarjan_Dfs(Node *cur, Node *fa, int depth) { cur->Father = fa; cur->DfsN = 1; cur->Depth = depth; for (Edge *e = cur->Head; e; e = e->Next) { if (e->To == cur->Father) continue ; Tarjan_Dfs(e->To, cur, depth + 1); e->To->UnFa = cur; } cur->DfsN = 2; for (Link *link = cur->TarjanHead; link; link = link->Next) { Node *other = link->In->Start == cur ? link->In->Target : link->In->Start; if (other->DfsN != 2) continue ; link->In->Lca = GetRoot(other); link->In->Dist = cur->Depth + other->Depth - link->In->Lca->Depth * 2; AddLink(link->In->Lca->AsLcaHead, link->In); } } void GetLca() { for ( int i = 1; i <= TotPath; i++) { AddLink(_paths[i].Start->TarjanHead, _paths + i); if (_paths[i].Start == _paths[i].Target) continue ; AddLink(_paths[i].Target->TarjanHead, _paths + i); } for ( int i = 1; i <= TotNode; i++) _nodes[i].UnFa = _nodes + i; Tarjan_Dfs(Root, NULL, 1); } void Dfs1(Node *cur, int *sDepthCnt) { int prev = sDepthCnt[cur->Depth + cur->W]; sDepthCnt[cur->Depth] += cur->AsSCnt; for (Edge *e = cur->Head; e; e = e->Next) { if (e->To == cur->Father) continue ; Dfs1(e->To, sDepthCnt); } cur->Ans += sDepthCnt[cur->Depth + cur->W] - prev; for (Link *link = cur->AsLcaHead; link; link = link->Next) sDepthCnt[link->In->Start->Depth]--; } void GetRunUp() { for ( int i = 1; i <= TotPath; i++) _paths[i].Start->AsSCnt++; memset (Bucket, 0, sizeof (Bucket)); Dfs1(Root, Bucket); } void Dfs2(Node *cur, int *bucket) { #define cnt(x) bucket[x + TotNode] int prev = cnt(cur->Depth - cur->W); for (Link *link = cur->AsTHead; link; link = link->Next) cnt(cur->Depth - link->In->Dist)++; for (Edge *e = cur->Head; e; e = e->Next) { if (e->To == cur->Father) continue ; Dfs2(e->To, bucket); } for (Link *link = cur->AsLcaHead; link; link = link->Next) { cnt(link->In->Target->Depth - link->In->Dist)--; assert (cnt(link->In->Target->Depth - link->In->Dist) >= 0); } cur->Ans += cnt(cur->Depth - cur->W) - prev; } void GetRunDown() { for ( int i = 1; i <= TotPath; i++) AddLink(_paths[i].Target->AsTHead, _paths + i); memset (Bucket, 0, sizeof (Bucket)); Dfs2(Root, Bucket); } void Read() { Root = _nodes + 1; scanf ( "%d%d" , &TotNode, &TotPath); for ( int i = 2; i <= TotNode; i++) { int u, v; scanf ( "%d%d" , &u, &v); AddEdge(_nodes + u, _nodes + v); AddEdge(_nodes + v, _nodes + u); } for ( int i = 1; i <= TotNode; i++) scanf ( "%d" , &_nodes[i].W); for ( int i = 1; i <= TotPath; i++) { int s, t; scanf ( "%d%d" , &s, &t); _paths[i].Start = _nodes + s; _paths[i].Target = _nodes + t; } } void Write() { bool isFirst = true ; for ( int i = 1; i <= TotNode; i++) { if (!isFirst) putchar ( ' ' ); isFirst = false ; printf ( "%d" , _nodes[i].Ans); } printf ( "\n" ); } int main() { Read(); GetLca(); GetRunUp(); GetRunDown(); Write(); return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步