1 条题解
-
0
自动搬运
来自洛谷,原作者为

NaCly_Fish
北海虽赊,扶摇可接。搬运于
2025-08-24 21:58:56,当前版本为作者最后更新于2019-09-20 16:15:39,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
解法:线段树分治 + LCT维护MST
好了现在赶紧自己试着写一个算是一道比较经典的线段树分治模型?(雾)
题目说了每条边只在一个时间段中出现,那么就可以按时间建一个线段树,把这些边放到线段树中,显然最终线段树中只会有 条边。
加完边之后就可以左右递归分治了,每走到一个节点,就把上面标记的边都加到 LCT 上,注意回溯的时候也要把这些边断掉。
每次走到叶节点,直接输出当前 LCT 中的边权和就是答案。
其中,加边的过程和普通的 LCT 维护 MST 做法一样,加边时找出连接这两点路径上权最大的边,把它断掉,再把现在这条边连上去。
当然这个过程中断掉的边,回溯的时候也要再连上。
可以证明,不会出现不合法的连/断边大概就是这样了,时间复杂度为 。
ps:不知道这个能不能叫“可回退化 LCT”?
#include<cstdio> #include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #define N 200003 #define reg register #define ll long long #define ls son[u][0] #define rs son[u][1] #define mid ((l+r)>>1) using namespace std; struct edge{ int u,v,w; inline edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){} }ed[N]; int a[N],st[N]; vector<int> adj[N*3]; int n,m; inline void read(int &x){ x = 0; char c = getchar(); while(c<'0'||c>'9') c = getchar(); while(c>='0'&&c<='9'){ x = (x<<3)+(x<<1)+(c^48); c = getchar(); } } struct Link_Cut_Tree{ //板子,没什么好说的 int rev[N],pos[N],fa[N],son[N][2]; inline void pushup(int u){ pos[u] = u; if(ls&&a[pos[ls]]>a[pos[u]]) pos[u] = pos[ls]; if(rs&&a[pos[rs]]>a[pos[u]]) pos[u] = pos[rs]; } inline void pushr(int u){ swap(ls,rs); rev[u] ^= 1; } inline void pushdown(int u){ if(!rev[u]) return; if(ls) pushr(ls); if(rs) pushr(rs); rev[u] = 0; } inline bool notrt(int u){ return son[fa[u]][0]==u||son[fa[u]][1]==u; } inline void rotate(int x){ int y = fa[x],z = fa[y]; int k = son[y][1]==x,w = son[x][k^1]; if(notrt(y)) son[z][son[z][1]==y] = x; son[x][k^1] = y; son[y][k] = w; if(w) fa[w] = y; fa[y] = x,fa[x] = z; pushup(y); } inline void splay(int x){ reg int y = x,z = 0; st[++z] = y; while(notrt(y)) st[++z] = y = fa[y]; while(z) pushdown(st[z--]); while(notrt(x)){ y = fa[x],z = fa[y]; if(notrt(y)) rotate((son[z][1]==y)==(son[y][1]==x)?y:x); rotate(x); } pushup(x); } inline void access(int u){ for(reg int v=0;u;u=fa[u]){ splay(u),rs = v; pushup(u),v = u; } } inline void makeroot(int u){ access(u),splay(u); pushr(u); } inline void split(int u,int v){ makeroot(u); access(v),splay(v); } inline void link(int u,int v){ makeroot(u); fa[u] = v; } inline void cut(int u,int v){ split(u,v); son[v][0] = fa[u] = 0; pushup(v); } inline int query(int u,int v){ split(u,v); return pos[v]; } inline int findroot(int u){ access(u),splay(u); pushdown(u); while(ls){ u = ls; pushdown(u); } splay(u); return u; } inline bool linked(int u,int v){ makeroot(u); return findroot(v)==u; } }T; const int lim = 32766; int s1[N],s2[N]; //这是两个栈,记录边和它的状态 删除/添加 int top,cnt; ll ans = 1; //答案要 +1 void insert(int nl,int nr,int l,int r,int u,int k){ if(nl<=l&&r<=nr){ //加入一条边,注意这个线段树不用也不能下传标记 adj[u].push_back(k); return; } if(nl<=mid) insert(nl,nr,l,mid,u<<1,k); if(nr>mid) insert(nl,nr,mid+1,r,u<<1|1,k); } void solve(int l,int r,int x){ int d,u,v,w,j,lst = top,ln = adj[x].size(); for(reg int i=0;i<ln;++i){ j = adj[x][i]; u = ed[j].u,v = ed[j].v; w = ed[j].w; if(T.linked(u,v)){ d = T.query(u,v)-n; if(ed[d].w<=w) continue; //当前边权比路径上最大的还大,就不用加进去 ans -= ed[d].w; T.cut(ed[d].u,d+n),T.cut(ed[d].v,d+n); s1[++top] = d; s2[top] = -1; } T.link(u,n+j),T.link(n+j,v); s1[++top] = j; s2[top] = 1; ans += w; } if(l==r) printf("%lld\n",ans); else{ solve(l,mid,x<<1); solve(mid+1,r,x<<1|1); } while(top>lst){ //回溯 d = s1[top]; u = ed[d].u,v = ed[d].v; w = ed[d].w; if(s2[top]==-1){ T.link(u,d+n),T.link(v,d+n); ans += w; }else{ T.cut(u,d+n),T.cut(v,d+n); ans -= w; } --top; } } int main(){ int l,r,u,v,w; read(n); for(reg int i=1;i<n;++i){ read(u),read(v),read(w); ed[++cnt] = edge(u,v,w); a[n+cnt] = w; insert(1,lim,1,lim,1,cnt); } read(m); for(reg int i=1;i<=m;++i){ read(u),read(v),read(w),read(l),read(r); ed[++cnt] = edge(u,v,w); a[n+cnt] = w; insert(l,r,1,lim,1,cnt); } solve(1,lim,1); return 0; }
- 1
信息
- ID
- 3214
- 时间
- 1000~7000ms
- 内存
- 500MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者