dsu on tree
dsu on tree
在处理子树询问时,子树与子树之间总是相互干扰.
常用的处理手法有线段树合并,dfs 序等来规避子树之间的干扰.
dsu on tree 则是利用的树链剖分轻重链的思想在多一个 的复杂度下避开子树之间干扰
算法模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void dfs2( int x, int ff, int op) { for ( int i=0;i<G[x].size();++i) { int v=G[x][i]; if (v==ff||v==son[x]) continue ; // 轻儿子不统计进去. dfs2(v, x, 0); } if (son[x]) { // 重儿子统计进去. dfs2(son[x], x, 1); } Son=son[x]; // 再将轻儿子统计进去,并计算答案 calc(x, ff); ans[x]=d; Son=0; if (op==0) mx=0,d=0,dele(x, ff); } |
时间复杂度分析:
每个点会在 dfs 的时候被访问一遍,为
一个点被 函数访问,当且仅当祖先有轻边,访问次数为轻边次数 .
一个点被 函数访问,也需要祖先有轻边,访问次数同样是 .
那么,所有点访问的总时间复杂度就是 .
特别注意:在 的时候删除所有信息就行,不必担心正在访问其他点之类的问题.
Escape Through Leaf
来源:CF932F
写一下 DP 式子发现是 .
对于子树中的 ,对祖先每一个 的贡献是关于 的一次函数.
如果只有一次子树询问显然可以用李超线段树来做.
由于每一个点都要求解答案,故采用 的方式进行求解,时间复杂度为 .
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 | #include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define ll long long #define pb push_back #define ls now<<1 #define rs now<<1|1 #define N 200009 #define setIO(s) freopen(s".in","r",stdin) using namespace std; const int inf=100003; const ll MA = 1ll<<50; struct Line { ll k,b; }line[N<<2]; vector< int >G[N]; ll dp[N]; int cn,n,a[N],b[N],size[N],son[N]; ll calc(Line o, int pos) { return 1ll*o.k*pos+o.b; } struct SGT { int tree[N<<2]; vector< int >clr; void update( int l, int r, int now, int L, int R, int x) { int mid=(l+r)>>1; if (!tree[now]) tree[now]=x,clr.pb(now); else { if (calc(line[tree[now]], mid) > calc(line[x], mid)) swap(tree[now], x); if (calc(line[x], l) < calc(line[tree[now]], l) && l!=r) update(l, mid, ls, L, R, x); if (calc(line[x], r) < calc(line[tree[now]], r) && l!=r) update(mid+1,r,rs,L,R,x); } } ll query( int l, int r, int now, int p) { if (l==r) { return tree[now] ? calc(line[tree[now]], l) : MA; } int mid = (l + r) >> 1; ll re=MA; if (tree[now]) re=min(re, calc(line[tree[now]], p)); if (p<=mid) return min(re, query(l,mid,ls,p)); else return min(re, query(mid+1,r,rs,p)); } void CLR() { for ( int i=0;i<clr.size();++i) tree[clr[i]]=0; clr.clear(); } }T; int Son; void upd( int x, int ff) { // y = b[x] ( ) + dp[x] ++cn; line[cn].k=b[x]; line[cn].b=dp[x]; T.update(-inf, inf, 1, -inf, inf, cn); for ( int i=0;i<G[x].size();++i) { int v=G[x][i]; if (v==ff) continue ; upd(v, x); } } void dfs1( int x, int ff) { size[x]=1,son[x]=0; for ( int i=0;i<G[x].size();++i) { int v=G[x][i]; if (v==ff) continue ; dfs1(v, x); size[x]+=size[v]; if (size[v]>size[son[x]]) son[x]=v; } } void dfs2( int x, int ff, int op) { for ( int i=0;i<G[x].size();++i) { int v=G[x][i]; if (v==ff||v==son[x]) continue ; dfs2(v, x, 0); // 不计子树影响. } // 计算重儿子影响. if (son[x]) dfs2(son[x], x, 1); // 算进轻儿子影响. for ( int i=0;i<G[x].size();++i) { int v=G[x][i]; if (v==ff||v==son[x]) continue ; upd(v, x); } // 计算当前点答案. if (size[x]==1) dp[x]=0; else { dp[x]=T.query(-inf, inf, 1, a[x]); } ++cn; line[cn].k=b[x]; line[cn].b=dp[x]; T.update(-inf, inf, 1, -inf, inf, cn); // 需要清除. if (op==0) T.CLR(),cn=0; } int main() { // setIO("input"); scanf ( "%d" ,&n); for ( int i=1;i<=n;++i) scanf ( "%d" ,&a[i]); for ( int i=1;i<=n;++i) scanf ( "%d" ,&b[i]); for ( int i=1;i<n;++i) { int x,y; scanf ( "%d%d" ,&x,&y); G[x].pb(y); G[y].pb(x); } dfs1(1, 0); dfs2(1, 0, 1); for ( int i=1;i<=n;++i) { printf ( "%lld " ,dp[i]); } return 0; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY