DDP

线段树+树剖/\(lct\)维护广义矩阵乘法

从例题开始讲

P4719

如果不带修改,那么就好做了

\(f_{i, 1 / 0}\) 表示 \(i\) 节点选或不选的最大权

容易得到转移

\[ f_{i, 0} = \sum_{son} max(f_{son, 0}, f_{son, 1}) \]

\[ f_{i, 1} = w_i+\sum_{son} f_{son, 0} \]

但是现在带修。

你会发现,一次修改只会影响一条链上的值,但不幸的是暴力修改仍是不可接受的

有什么方法可以快速处理树链?树剖,(LCT)

现在的问题是如何快速维护一条树链的 \(DP\)

我们先引入 广义矩阵乘法

image

那么这根我们的 \(DP\) 有什么关系呢?

我们尝试将 \(DP\) 写成矩阵的形式

为了保证复杂度,我们定义 \(g_{i, 0/1}\) 表示只考虑 \(i\) 的轻儿子, \(i\) 选或不选的最大值

那么有 (\(son\) 为重儿子)

\[ f_{i, 0} = g_{i, 0} + max(f_{son, 1}, f_{son, 0}) \]

\[ f_{i, 1} = g_{i,1} + f_{son, 0} \]

把他写成广义矩阵

\[ \begin{bmatrix} g_{i,0} & g_{i,0}\\ g_{i,1} & -\infty \end{bmatrix}\times \begin{bmatrix} f_{son,0}\\f_{son,1} \end{bmatrix}= \begin{bmatrix} f_{i,0}\\f_{i,1} \end{bmatrix} \]

使用树剖+线段树维护区间转移矩阵,就能快速转移一条重链,而修改只需修改他的 \(g_{i, 1}\)和向上的一条链

image

code
#include<cstring>
#include<algorithm>
#include<queue>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
	int x = 0; bool f = 0;char c = getchar();
	while(!isdigit(c)){f = c == '-';c = getchar();}
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return f ? -x : x;
}
const int maxn = 100005;

struct matrix{
	int a[2][2];
	matrix(){memset(a, -0x3f, sizeof(a));}
	matrix operator *(const matrix b){
		matrix ans;
		for(int i = 0; i < 2; ++i)
			for(int k = 0; k < 2; ++k)
				for(int j = 0; j < 2; ++j)
					ans.a[i][j] = max(ans.a[i][j], a[i][k] + b.a[k][j]);
		return ans;
	}
};
int n, m, a[maxn];
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
}
int son[maxn], fa[maxn], top[maxn], ed[maxn], size[maxn], dep[maxn];
matrix val[maxn];
int f[maxn][2];

void dfs1(int x){
	size[x] = 1;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa[x])continue;
		dep[v] = dep[x] + 1, fa[v] = x;
		dfs1(v);
		size[x] += size[v];
		son[x] = size[son[x]] > size[v] ? son[x] : v;
		f[x][0] += max(f[v][0], f[v][1]);
		f[x][1] += f[v][0];
	}
	f[x][1] += a[x];
}
int tim, id[maxn], dfn[maxn];

void dfs2(int x, int tp){
	dfn[x] = ++tim; id[tim] = x; top[x] = tp;
	ed[tp] = tim;
	if(son[x])dfs2(son[x], tp);
	val[x].a[1][0] = a[x];
	val[x].a[0][1] = 0;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == son[x] || v == fa[x])continue;
		dfs2(v, v);
		val[x].a[0][1] += max(f[v][1], f[v][0]);
		val[x].a[1][0] += f[v][0];
	}
	val[x].a[0][0] = val[x].a[0][1];
}

struct seg{
	matrix t[maxn << 2 | 1];
	void push_up(int x){t[x] = t[x << 1] * t[x << 1 | 1];}
	void built(int x, int l, int r){
		if(l == r){t[x] = val[id[l]]; return;}
		int mid = (l + r) >> 1;
		built(x << 1, l, mid);
		built(x << 1 | 1, mid + 1, r);
		push_up(x);
	}
	matrix query(int x, int l, int r, int L, int R){
		if(L <= l && r <= R)return t[x];
		int mid = (l + r) >> 1;
		if(R <= mid)return query(x << 1, l, mid, L, R);
		if(L > mid)return query(x << 1 | 1, mid + 1, r, L, R);
		return query(x << 1, l, mid, L, R) * query(x << 1 | 1, mid + 1, r, L, R);
	}
	void modify(int x, int l, int r, int pos){
		if(l == r){
			t[x] = val[id[pos]];
			return;
		}
		int mid = (l + r) >> 1;
		if(pos <= mid)modify(x << 1, l, mid, pos);
		else modify(x << 1 | 1, mid + 1, r, pos);
		push_up(x);
	}
}t;

void upd(int x, int y){
	val[x].a[1][0] += y - a[x]; a[x] = y;
	matrix las, now;
	while(x){
		las = t.query(1, 1, n, dfn[top[x]], ed[top[x]]);
		t.modify(1, 1, n, dfn[x]);
		now = t.query(1, 1, n, dfn[top[x]], ed[top[x]]);
		x = fa[top[x]];
		val[x].a[0][0] += max(now.a[0][0], now.a[1][0]) - max(las.a[0][0], las.a[1][0]);
		val[x].a[0][1] = val[x].a[0][0];
		val[x].a[1][0] += now.a[0][0] - las.a[0][0];
	}
}

int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		add(u, v); add(v, u);
	}
	dfs1(1); dfs2(1, 1);
	t.built(1, 1, n);
	for(int i = 1; i <= m; ++i){
		int x = read(), y = read();
		upd(x, y);
		matrix ans = t.query(1, 1, n, dfn[1], ed[1]);
		printf("%d\n",max(ans.a[0][0], ans.a[1][0]));
	}
	return 0;
}

最小权覆盖集 \(=\) 全集 \(-\) 最大权独立集

然后嘞?怎么处理制选和强制不选?

现在思考一下如何转化为上一个问题。

强制不选设为 \(-inf\) 强制选设为 \(inf\) 并给全集加上 \(inf\)

然后就跟上个题一样了。就是常数大了点。

这个题其实可以用树上倍增的做法,其实也差不多。

我们的\(CSP\)题。。。

部分分看着拿。

\(k = 1\)直接剖

\(k = 2\) 不会到链外(图),于是如果把链抽出来就是

\(f_{i} = w_i + min(f_{i - 1}, f_{i - 2})\)

不妨设 \(f_{i, 0 / 1 / 2}\) 表示走到距离点 \(i\) 距离为 \(0 / 1 / 2\) 的权值

上面的式子可以简单改写一下。

\(k = 3\)

一定只会跳到邻接点(图)

预处理 \(g_i\) 表示 \(i\) 邻接点最小权值

可以写出转移

\[ f_{i, 0} = min(f_{las, 0}, f_{las, 1}, f_{las, 2}) + w_i \]

\[ f_{i, 1} = min(f_{las, 0}, f_{las, 1} + g_i) \]

\[ f_{i, 2} = f_{las, 1} \]

以上转化成广义矩阵乘法形式可以使用树剖等手法维护。

posted @ 2024-07-21 21:01  Chen_jr  阅读(58)  评论(0编辑  收藏  举报