luogu2680 运输计划
题目大意
给出一棵有$n$个节点的带边权的树,另从中选出$m$条链,要求你从树边中将其中一个改为0,使得图中的最大链长最短。$n,m\leq 300000$。
题解
总体算法
又是一道图上的反演的题。如果结果中图中的最大链长len,那么len越小,对边的要求就越苛刻。边的要求包括:1.如果我们将所有选中的链按链长排序,那么这条边被包含在后缀多少条链中;2.边的长度。苛刻的体现在于如果len越小,那么我们能找到一条边,使得删去这条边后图中最大链的长度<=len的可能性越小。这样我们就可以用LowerBound求。
如何判定下划线部分?
我们先找到所有链长大于len的链,将它经过的边(用其To节点表示)全部打上标记,然后枚举所有标记的边,如果最大链-该边的边权<=len,则len可行。
怎么打标记呢?我们要用到树上差分的方法。每个节点存储的值为delta,表示该节点子树中的值的和减去该节点子树中不包含该节点的值的和。本道题中,值为边所在选中链的个数。更改时,将链首链尾的delta+1,然后将链首链尾的LCA的delta-2即是。delta都求出了,最后一遍Dfs,在回溯的时候求和即可。
不过本道题卡常数卡得比较厉害,调用堆栈使得常数变大。怎么办?生成一个数组,下标为Dfs时间戳,值为节点指针,每次按照数组的顺序节点向父节点刷新即可。
另外,本题LCA要用Tarjan求,少用vector。
#include <cstdio> #include <cstring> #include <algorithm> #include <stack> using namespace std; char get_char() { static char buf[10000001]; static char *cur, *end; if (cur == end) { cur = buf; end = buf + fread(buf, sizeof(char), sizeof(buf), stdin); if (cur == end) return EOF; } return *cur++; } int read() { int a = 0, f = 1; char ch = get_char(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = get_char(); } while (ch >= '0' && ch <= '9') { a = a * 10 + ch - '0'; ch = get_char(); } return a * f; } //------------------------------------------------------ #define UpdateMax(x, y) x = max(x, y) #define UpdateMin(x, y) x = min(x, y) const int MAX_NODE = 300010, MAX_PATH = 300010, MAX_LOG = 30; int MaxPathLen; struct Node; struct Edge; struct Path; struct Link; struct Node { int DfsN, InPathCnt, Dist, ToFaW; Edge *Head; Node *UnFa;//UnionFather Node *Father; Link *HeadLink; }_nodes[MAX_NODE], *Root, *TopList[MAX_NODE]; int TotNode; struct Edge { Node *To; Edge *Next; int Weight; }_edges[MAX_NODE * 2]; int _eCount; struct Path { Node *From, *To; Node *Lca; int SumW; }_paths[MAX_PATH]; int TotPath; struct Link { Node *To; Path *Query; Link *Next; }_links[MAX_PATH * 2]; int LinkCnt; void AddEdge(Node *from, Node *to, int w) { Edge *e = _edges + ++_eCount; e->To = to; e->Weight = w; e->Next = from->Head; from->Head = e; } void AddLink(Node *from, Node *to, Path *query) { Link *cur = _links + ++LinkCnt; cur->To = to; cur->Query = query; cur->Next = from->HeadLink; from->HeadLink = cur; } Node *GetRoot(Node *cur) { return cur->UnFa == cur ? cur : cur->UnFa = GetRoot(cur->UnFa); } void Tarjan(Node *cur, Node *fa, int dist, int toFaW) { cur->DfsN = 1; cur->UnFa = cur; cur->Father = fa; cur->ToFaW = toFaW; cur->Dist = dist; for (Edge *e = cur->Head; e; e = e->Next) { if (e->To == cur->Father) continue; Tarjan(e->To, cur, dist + e->Weight, e->Weight); e->To->UnFa = cur; } for (Link *link = cur->HeadLink; link; link = link->Next) if (link->To->DfsN == 2) { link->Query->Lca = GetRoot(link->To); link->Query->SumW = cur->Dist + link->To->Dist - link->Query->Lca->Dist * 2; } cur->DfsN = 2; } void InitAllPath() { for (int i = 1; i <= TotPath; i++) { AddLink(_paths[i].From, _paths[i].To, _paths + i); AddLink(_paths[i].To, _paths[i].From, _paths + i); } Tarjan(Root, NULL, 0, 0); for (int i = 1; i <= TotPath; i++) UpdateMax(MaxPathLen, _paths[i].SumW); } void Build(int u, int v, int w) { AddEdge(_nodes + u, _nodes + v, w); AddEdge(_nodes + v, _nodes + u, w); } void Read() { TotNode = read(), TotPath = read(); for (int i = 1; i <= TotNode - 1; i++) { int u, v, w; u = read(), v = read(), w = read(); Build(u, v, w); } for (int i = 1; i <= TotPath; i++) { int u, v; u = read(), v = read(); _paths[i].From = _nodes + u; _paths[i].To = _nodes + v; } Root = _nodes + 1; } void Top_Dfs(stack<Node*> &st, Node *cur) { for (Edge *e = cur->Head; e; e = e->Next) { if (e->To == cur->Father) continue; Top_Dfs(st, e->To); } st.push(cur); } void GetTopList() { static stack<Node*> st; Top_Dfs(st, Root); for (int i = 1; i <= TotNode; i++) { TopList[i] = st.top(); st.pop(); } } void GetInPath() { for (int i = TotNode; i >= 2; i--) TopList[i]->Father->InPathCnt += TopList[i]->InPathCnt; } bool CanBeAns(const int ans) { for (int i = 1; i <= TotNode; i++) _nodes[i].InPathCnt = 0; int maxD = 0, sum = 0; for (int i = 1; i <= TotPath; i++) { if (_paths[i].SumW > ans) { sum++; UpdateMax(maxD, _paths[i].SumW - ans); _paths[i].From->InPathCnt++; _paths[i].To->InPathCnt++; _paths[i].Lca->InPathCnt -= 2; } } GetInPath(); for (int i = 1; i <= TotNode; i++) if (_nodes[i].InPathCnt == sum && _nodes[i].ToFaW >= maxD) return true; return false; } int LowerBound(int l, int r, bool(*InLowerRange)(int)) { if (!InLowerRange(r)) return -1; while (l < r) { int mid = (l + r) / 2; if (InLowerRange(mid)) r = mid; else l = mid + 1; } return l; } int main() { Read(); InitAllPath(); GetTopList(); printf("%d\n", LowerBound(0, MaxPathLen, CanBeAns)); return 0; }