题目大意
游戏的地图可以看作一一棵包含 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时,特判路径起终点相同的情况。
#include#include #include #include 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;}