树链剖分

前置芝士

子树大小

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最大)的结点;
  • 轻儿子:父亲节点中除了重儿子以外的儿子;
  • 重边:父亲结点和重儿子连成的边;
  • 轻边:父亲节点和轻儿子连成的边;
  • 重链:由多条重边连接而成的路径;
  • 轻链:由多条轻边连接而成的路径;

[变量定义]

img

[算法流程]

  • 第一遍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;
}

posted @ 2023-11-02 11:25  White_Sheep  阅读(5)  评论(0编辑  收藏  举报