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;
}

浙公网安备 33010602011771号