树链剖分
前置芝士
子树大小
int size[N];
void dfs(int u,int fa){
size[u]=1;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(v==fa) continue;
dfs(v,u);
size[u]+=size[v];
}
}
重链剖分
树链剖分->线段树维护->树上修改与查询
- 重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;
- 轻儿子:父亲节点中除了重儿子以外的儿子;
- 重边:父亲结点和重儿子连成的边;
- 轻边:父亲节点和轻儿子连成的边;
- 重链:由多条重边连接而成的路径;
- 轻链:由多条轻边连接而成的路径;
[变量定义]
[算法流程]
- 第一遍dfs1,求出fa,dep,son,sz数组
- 第二遍dfs2,求出top,id,nw数组
[性质]
整棵树会被剖分成若干条重链
轻儿子一定是每条重链的顶点
任意一条路径被切分成不超过logn条重链
长链剖分
根据子树的深度把树拆分成若干条互不相交的长链,用来优化与深度有关的树上DP。
[性质]
(1)一个节点到它所在的长链的链底部的路径,为从这个节点到它子树每个子树所有节点的路径中,最长的一条。
(2)一个节点到根的路径,最多经过O(sqrt(n))个虚边。
树上操作
[problem description]
有一棵点数为 N 的树,以点 1 为根,且树有点权。然后有 M 个操作,分为三种:
- 操作 1 :把某个节点 x 的点权增加 a 。
- 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
- 操作 3 :询问某个节点 x 到根的路径中所有点的点权和。
[input]
第一行包含两个整数 N, M 。表示点数和操作数。
接下来一行 N 个整数,表示树中节点的初始权值。
接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。
再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。
[output]
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。
[sample]
in
5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3
out
6
9
13
[datas]
N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6
[solved]
const int N = 100010;
vector<int> e[N];
int n, m; //节点数和操作数
int w[N];//节点权值
//dep:深度,sz:子树大小,fa:父亲节点,son:重儿子
int dep[N], sz[N], fa[N], son[N];//dfs1
//id:节点新id,nw:映射,id[u]=cnt,nw[cnt]=w[u]
int id[N], nw[N], top[N], cnt;//dfs2
void dfs1(int u, int father) {
fa[u] = father, dep[u] = dep[father] + 1, sz[u] = 1;
for (int v : e[u]) {
if (v == father) continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int t) {
id[u] = ++cnt; nw[cnt] = w[u];
top[u] = t;
if (!son[u]) return;
dfs2(son[u], t);
for (int v : e[u]) {
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
struct Tree {
int l, r;
ll add, sum;
} tr[N * 8];
void pushup(int u) {
tr[u].sum = tr[lc].sum + tr[rc].sum;
}
void pushdown(int u) {
if (tr[u].add) {
tr[lc].sum += tr[u].add * (tr[lc].r - tr[lc].l + 1);
tr[rc].sum += tr[u].add * (tr[rc].r - tr[rc].l + 1);
tr[lc].add += tr[u].add;
tr[rc].add += tr[u].add;
tr[u].add = 0;
}
}
void build(int u, int l, int r) {
tr[u] = {l, r, 0, nw[l]};
if (l == r) return;
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
pushup(u);
}
void update(int u, int l, int r, int k) {
if (l <= tr[u].l && r >= tr[u].r) {
tr[u].add += k;
tr[u].sum += (ll)k * (tr[u].r - tr[u].l + 1);
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update(lc, l, r, k);
if (r > mid) update(rc, l, r, k);
pushup(u);
}
//修改[u,v]路径
void update_path(int u, int v, int k) {
while (top[u] != top[v]) {//每次让u是深节点
if (dep[top[u]] < dep[top[v]]) swap(u, v);
update(1, id[top[u]], id[u], k);
u = fa[top[u]];
}
//最后可能一个是另一个的祖先节点
if (dep[u] < dep[v]) swap(u, v);
update(1, id[v], id[u], k);
}
//修改以u为根的子树区间
void update_tree(int u, int k) {
update(1, id[u], id[u] + sz[u] - 1, k);
}
//修改u的单点
void update_node(int u, int k) {
update(1, id[u], id[u], k);
}
ll query(int u, int l, int r) {
if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
ll res = 0;
if (l <= mid) res += query(lc, l, r);
if (r > mid) res += query(rc, l, r);
return res;
}
//查询[u,v]路径
ll query_path(int u, int v) {
ll res = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
res += query(1, id[top[u]], id[u]);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
res += query(1, id[v], id[u]);
return res;
}
//查询u为根的子树区间
ll query_tree(int u) {
return query(1, id[u], id[u] + sz[u] - 1);
}
void solve() {
cin >> n >> m;
// cout<<n<<m<<endl;
for (int i = 1; i <= n; i++) cin >> w[i];
for (int i = 1, x, y; i < n; i++) {
cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs1(1, -1);
dfs2(1, 1);
build(1, 1, cnt);
int op, x, k;
for (int i = 1; i <= m; i++) {
cin >> op;
if (op == 1) {
cin >> x >> k;
update_node(x, k);
} else if (op == 2) {
cin >> x >> k;
update_tree(x, k);
} else {
cin >> x;
cout << query_path(1, x) << endl;
}
}
}
Dominant Indices
[problem description]
以 1 为根,n 个节点的树。设 d(u,x) 为 u 子树中到 u 距离为 x 的节点数。
对于每个点,求一个最小的 k,使得 d(u,k) 最大。
[solved]
给定一棵树,每个点有点权,选定k个叶子,满足根到k个叶子的所有路径所覆盖的点权和最大。
贪心的去选择,每次选择最大的路径,然后将路径上所有点的权值清零。
那么我们可以用长链剖分来实现这个贪心。
链长改为最大的路径权值和,这样子把每条重链的权值丢进一个堆里面取k次即可。
const int N=200010;
int n,k,a[N];
struct E{
int v,ne;
}e[N*2];
int idx,h[N];
int son[N];
ll f[N],dep[N];
void add(int x,int y){
e[++idx].v=y;
e[idx].ne=h[x];
h[x]=idx;
}
void dfs(int u,int fa){
for(int i=h[u];i;i=e[i].ne){
int v=e[i].v;
if(v==fa) continue;
f[v]=dep[v]=dep[u]+a[v];
dfs(v,u);
if(f[v]>f[u]) f[u]=f[v],son[u]=v;
}
}
int top[N];
void dfs1(int u,int fa,int topf){
top[u]=topf;
if(son[u]) dfs1(son[u],u,topf);
for(int i=h[u];i;i=e[i].ne){
int v=e[i].v;
if(v==fa||v==son[u]) continue;
dfs1(v,u,v);
}
}
void solve() {
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int x,y,z;
cin>>x>>y;
add(x,y);
add(y,x);
}
dep[1]=f[1]=a[1];
dfs(1,0);
dfs1(1,0,1);
vector<ll> ans;
for(int i=1;i<=n;i++){
if(top[i]==i) ans.push_back(f[i]-dep[i]+a[i]);
}
sort(ans.begin(),ans.end(),greater<ll>());
ll res=0;
for(int i=0;i<min(k,(int)ans.size());i++) res+=ans[i];
cout<<res<<endl;
}