dsu on tree
dsu on tree
在处理子树询问时,子树与子树之间总是相互干扰.
常用的处理手法有线段树合并,dfs 序等来规避子树之间的干扰.
dsu on tree 则是利用的树链剖分轻重链的思想在多一个 $O(\log n)$ 的复杂度下避开子树之间干扰
算法模板:
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 的时候被访问一遍,为 $O(n).$
一个点被 $\mathrm{calc}$ 函数访问,当且仅当祖先有轻边,访问次数为轻边次数 $O(\log n)$.
一个点被 $\mathrm{delete}$ 函数访问,也需要祖先有轻边,访问次数同样是 $O(\log n)$.
那么,所有点访问的总时间复杂度就是 $O(n \log n)$.
特别注意:在 $\mathrm{delete}$ 的时候删除所有信息就行,不必担心正在访问其他点之类的问题.
Escape Through Leaf
来源:CF932F
写一下 DP 式子发现是 $\mathrm{dp[x]=dp[y]+a[x] \times b[y]}$.
对于子树中的 $\mathrm{y}$,对祖先每一个 $\mathrm{dp[x]}$ 的贡献是关于 $\mathrm{a[x]}$ 的一次函数.
如果只有一次子树询问显然可以用李超线段树来做.
由于每一个点都要求解答案,故采用 $\mathrm{DSU}$ 的方式进行求解,时间复杂度为 $O(n \log^2 n)$.
#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; }