动态 DP 笔记

动态 DP

前言:DP+修改=动态 DP

矩阵乘法

矩阵

矩阵是一个按照长方阵排列的复数或实数集合。在信息中的意义就是一个二维数组。

m × n m\times n m×n 个数 a i j a_{ij} aij 排列成 m m m n n n 列的二维数组称为 m m m n n n 列的矩阵,简称为 m × n m\times n m×n 矩阵。记作 A = [ a 11 a 12 … a 1 n a 21 a 22 … a 2 n … … a m 1 a m 2 … a m n ] A=\left[\begin{matrix}a_{11}&a_{12}&\dots&&a_{1n}\\a_{21}&a_{22}&\dots&&a_{2n}\\&\dots&\dots\\a_{m1}&a_{m2}&\dots&&a_{mn}\end{matrix}\right] A= a11a21am1a12a22am2a1na2namn

其中 a i j a_{ij} aij A A A 中的第 ( i , j ) (i,j) (i,j) 个元素。

矩阵加法

只有当矩阵 A A A 的列数和矩阵 B B B 的行数和列数均相等时才有意义,矩阵加法有交换律。若 A , B A,B A,B m × n m\times n m×n 的矩阵,记 C = A + B C=A+B C=A+B c i j = a i j + b i j c_{ij}=a_{ij}+b_{ij} cij=aij+bij

普通矩阵乘法

普通矩阵乘法(又称 Hadamard product,Kronecker product)只有当矩阵 A A A 的列数和矩阵 B B B 的行数相等时才有意义,所以矩阵乘法没有交换律。若 A A A m × d m\times d m×d 的矩阵, B B B d × n d\times n d×n 的矩阵,记 C = A × B C=A\times B C=A×B c i j = ∑ k = 1 d a i k × b k j c_{ij}=\sum_{k=1}^d a_{ik}\times b_{kj} cij=k=1daik×bkj

例: [ 1 1 4 5 1 4 ] × [ 1 9 1 9 8 1 ] = [ 34 22 38 58 ] \left[\begin{matrix}1&1&4\\5&1&4\end{matrix}\right]\times\left[\begin{matrix}1&9\\1&9\\8&1\end{matrix}\right]=\left[\begin{matrix}34&22\\38&58\end{matrix}\right] [151144]× 118991 =[34382258]

for (int i = 0; i < m; i++)
	for (int j = 0; j < n; j++)
		for (int k = 0; k < d; k++)
			c[i][j] += a[i][k] * b[k][j];

单位矩阵:若 A A A 是一个 m × n m\times n m×n 的矩阵,且 A × I = A A\times I=A A×I=A,则 I I I 是一个 n n n 阶单位矩阵,简记为 I n I_n In(此处可以同乘法中的 1 1 1 做类比

广义矩阵乘法

常用的: c i j = max ⁡ k = 1 d a i k + b k j c i j = min ⁡ k = 1 d a i k + b k j c_{ij}=\max_{k=1}^d{a_{ik}+b_{kj}}\\c_{ij}=\min_{k=1}^d{a_{ik}+b_{kj}} cij=k=1maxdaik+bkjcij=k=1mindaik+bkj

矩阵乘法的结合律

D = ( A × B ) × C = A × ( B × C ) D=(A\times B) \times C=A\times (B \times C) D=(A×B)×C=A×(B×C)
其中 A A A m × d 1 m\times d_1 m×d1 的矩阵, B B B d 1 × d 2 d_1\times d_2 d1×d2 的矩阵, C C C d 2 × n d_2\times n d2×n 的矩阵, D D D m × n m\times n m×n

证: D i j = ∑ k 2 = 1 d 2 ( ∑ k 1 = 1 d 1 a i k 1 × b k 1 k 2 ) × c k 2 j = ∑ k 2 = 1 d 2 ∑ k 1 = 1 d 1 a i k 1 × b k 1 k 2 × c k 2 j = ∑ k 1 = 1 d 1 a i k 1 × ∑ k 2 = 1 d 2 ( b k 1 k 2 × c k 2 j ) D_{ij}=\sum^{d_2}_{k_2=1}({\sum_{k_1=1}^{d_1}a_{ik_1}\times b_{k_1k_2}})\times c_{k_2j}\\=\sum^{d_2}_{k_2=1}{\sum_{k_1=1}^{d_1}a_{ik_1}\times b_{k_1k_2}}\times c_{k_2j}\\=\sum_{k_1=1}^{d_1}a_{ik_1}\times \sum^{d_2}_{k_2=1}(b_{k_1k_2}\times c_{k_2j}) Dij=k2=1d2(k1=1d1aik1×bk1k2)×ck2j=k2=1d2k1=1d1aik1×bk1k2×ck2j=k1=1d1aik1×k2=1d2(bk1k2×ck2j)

(广义矩阵乘法同理

既然有结合律,那就有快速幂。

矩阵快速幂

相信大家都会正常的快速幂(如下,递归的更好理解一些)。

int fpow(int b, int p) {
	if (p == 0)
		return 1;
	if (p == 1)
		return b;
	int t = fpow(b, p >> 1)	//同 fpow(b, p / 2)
	t = t * t;
	if (p & 1)	//同 p % 2 == 1
		return t * b;
	return t;
}

矩阵快速幂就是把其中的 int 改为 mat,把 1 改为 I 即可。

mat fpow(mat b, int p) {
	if (p == 0)
		return I;
	if (p == 1)
		return b;
	mat t = fpow(b, p >> 1)	//同 fpow(b, p / 2)
	t = t * t;
	if (p & 1)	//同 p % 2 == 1
		return t * b;
	return t;
}

矩阵乘法加速 DP

快速幂加速

对于一个递推式,如斐波那契数列的递推式 f ( x ) = f ( x − 1 ) + f ( x − 2 ) f(x)=f(x-1)+f(x-2) f(x)=f(x1)+f(x2),我们可以将其转化为矩阵形式,即:

[ f ( x ) f ( x − 1 ) ] = [ f ( x − 1 ) f ( x − 2 ) ] × C \left[\begin{matrix}f(x)&f(x-1)\end{matrix}\right]=\left[\begin{matrix}f(x-1)&f(x-2)\end{matrix}\right]\times C [f(x)f(x1)]=[f(x1)f(x2)]×C

我们只需要构造矩阵 C C C 即可使用矩阵快速幂加速其运算。

先确认 C C C 是一个 2 × 2 2\times2 2×2 的矩阵,形如 [ a b c d ] \left[\begin{matrix}a&b\\c&d\end{matrix}\right] [acbd]

根据 f ( x ) = f ( x − 1 ) + f ( x − 2 ) f(x)=f(x-1)+f(x-2) f(x)=f(x1)+f(x2) 可得
a × f ( x − 1 ) + c × f ( x − 2 ) = f ( x ) b × f ( x − 1 ) + d × f ( x − 2 ) = f ( x − 1 ) a\times f(x-1)+c\times f(x-2)=f(x)\\ b\times f(x-1)+d\times f(x-2)=f(x-1) a×f(x1)+c×f(x2)=f(x)b×f(x1)+d×f(x2)=f(x1)

最终解得 a = b = c = 1 , d = 0 a=b=c=1,d=0 a=b=c=1,d=0,即 C = [ 1 1 1 0 ] C=\left[\begin{matrix}1&1\\1&0\end{matrix}\right] C=[1110]

const int mod = 1000000007;
const int INF = 1145141919810;
const int maxn = 500;
struct mat {
	vector<vector<int>> matrix;
    mat(vector<vector<int>> m_) {
        matrix = m_;
    }
	mat(int n = 0, int m = 0) {
		matrix.resize(n, vector<int>(m));
	}
	void resize(int n, int m) {
		matrix.resize(n, vector<int>(m));
	}
	vector<int>& operator[](int i) {
		return matrix[i];
	}
	friend mat operator+(mat a, mat b) {
		int n = a.size().first, m = a.size().second;
		mat ans(n, m);
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				ans[i][j] = (a[i][j] + b[j][j]) % mod;
		return ans;
	}
	static mat unite(int n) {
		mat I; I.resize(n, n);
		for (int i = 0; i < n; i++)
			I[i][i] = 1;
		return I;
	}
	pair<int, int> size() {
		return { matrix.size(), matrix[0].size() };
	}
	friend mat operator*(mat a, mat b) {
		int n = a.size().first, m = a.size().second, s = b.size().second;
		mat ans(n, s);
		for (int i = 0; i < n; i++)
			for (int j = 0; j < s; j++)
				for (int k = 0; k < m; k++)
					ans[i][j] = (ans[i][j] + a[i][k] * b[k][j]) % mod;
		return ans;
	}
	friend mat operator~(mat k) {
		int n = k.size().first, m = k.size().second;
		mat ans(m, n);
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				ans[i][j] = k[j][i];
		return ans;
	}
};
mat fpow(mat x, int p) {
    mat base = x;
    mat ans = mat::unite(2);
    while (p) {
        int t = p & 1;
        p >>= 1;
        if (t)
            ans = ans * base;
        base = base * base;
    }
    return ans;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
    mat it(1, 2);
    it[0][0] = 1, it[0][1] = 1;
    mat base({ {1, 1}, {1, 0} });
    int n; cin >> n;
    if (n <= 2) {
        cout << "1" << endl;
        return 0;
    }
    cout << (it * fpow(base, n - 2))[0][0] << endl;
}

例:P1962,P1349

矩阵乘法+线段树

引入:P7453 [THUSCH2017] 大魔法师

n n n 个元素,每个元素有三种属性 A i , B i , C i A_i, B_i, C_i Ai,Bi,Ci,有几种操作:

  1. [ l , r ] [l,r] [l,r] 区间内令 A i = A i + B i A_i=A_i+B_i Ai=Ai+Bi
  2. [ l , r ] [l,r] [l,r] 区间内令 B i = B i + C i B_i=B_i+C_i Bi=Bi+Ci
  3. [ l , r ] [l,r] [l,r] 区间内令 C i = C i + A i C_i=C_i+A_i Ci=Ci+Ai
  4. [ l , r ] [l,r] [l,r] 区间内令 A i = A i + v A_i=A_i+v Ai=Ai+v
  5. [ l , r ] [l,r] [l,r] 区间内令 B i = B i × v B_i=B_i\times v Bi=Bi×v
  6. [ l , r ] [l,r] [l,r] 区间内令 C i = v C_i=v Ci=v
  7. 询问区间每个属性的和。

答案对 998244353 998244353 998244353 取模。
n , m ≤ 2.5 × 1 0 5 , 0 ≤ A i , B i , C i , v < 998244353 n,m\le2.5\times 10^5,0\le A_i,B_i,C_i,v<998244353 n,m2.5×105,0Ai,Bi,Ci,v<998244353

解:

如果使用线段树大法,4-7 操作还是较为简单的,但是1-3操作就很烦人。

所以我们对每个元素构造矩阵 [ A i B i C i 1 ] \left[\begin{matrix}A_i&B_i&C_i&1\end{matrix}\right] [AiBiCi1],那搞个矩阵有什么好处捏,其实我们可以用矩阵的形式将操作都表示出来。

例如

操作 1(2,3同理): [ A i + B i B i C i 1 ] = [ A i B i C i 1 ] × [ 1 0 0 0 1 1 0 0 0 0 1 0 0 0 0 1 ] \left[\begin{matrix}A_i+B_i&B_i&C_i&1\end{matrix}\right]=\left[\begin{matrix}A_i&B_i&C_i&1\end{matrix}\right]\times\left[\begin{matrix}1&0&0&0\\1&1&0&0\\0&0&1&0\\0&0&0&1\end{matrix}\right] [Ai+BiBiCi1]=[AiBiCi1]× 1100010000100001

操作4(5,6同理): [ A i + v B i C i 1 ] = [ A i B i C i 1 ] × [ 1 0 0 0 0 1 0 0 0 0 1 0 v 0 0 1 ] \left[\begin{matrix}A_i+v&B_i&C_i&1\end{matrix}\right]=\left[\begin{matrix}A_i&B_i&C_i&1\end{matrix}\right]\times\left[\begin{matrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\v&0&0&1\end{matrix}\right] [Ai+vBiCi1]=[AiBiCi1]× 100v010000100001

辣么询问怎么办捏,当然是线段树维护矩阵加法啦!

我们只需要搞一个支持区间乘法,维护区间加法的线段树就行啦!!!

ps:这道题需要稍微卡卡常(至少我需要

//我的脚标从 0 开始,线段树区间左闭右开
const int mod = 998244353;
const int INF = 1145141919810;
const int maxn = 500;
struct mat {
	int matrix[4][4];
	int n, m;
	mat(int n_ = 0, int m_ = 0) {
		n = n_;
		m = m_;
		memset(matrix, 0, sizeof(int) * 16);
	}
	inline friend mat operator+(const mat& a, const mat& b) {
		int n = a.n, m = a.m;
		mat ans(n, m);
		ans.matrix[0][0] = (a.matrix[0][0] + b.matrix[0][0]) % mod;
		ans.matrix[0][1] = (a.matrix[0][1] + b.matrix[0][1]) % mod;
		ans.matrix[0][2] = (a.matrix[0][2] + b.matrix[0][2]) % mod;
		ans.matrix[0][3] = (a.matrix[0][3] + b.matrix[0][3]) % mod;
		ans.matrix[1][0] = (a.matrix[1][0] + b.matrix[1][0]) % mod;
		ans.matrix[1][1] = (a.matrix[1][1] + b.matrix[1][1]) % mod;
		ans.matrix[1][2] = (a.matrix[1][2] + b.matrix[1][2]) % mod;
		ans.matrix[1][3] = (a.matrix[1][3] + b.matrix[1][3]) % mod;
		ans.matrix[2][0] = (a.matrix[2][0] + b.matrix[2][0]) % mod;
		ans.matrix[2][1] = (a.matrix[2][1] + b.matrix[2][1]) % mod;
		ans.matrix[2][2] = (a.matrix[2][2] + b.matrix[2][2]) % mod;
		ans.matrix[2][3] = (a.matrix[2][3] + b.matrix[2][3]) % mod;
		ans.matrix[3][0] = (a.matrix[3][0] + b.matrix[3][0]) % mod;
		ans.matrix[3][1] = (a.matrix[3][1] + b.matrix[3][1]) % mod;
		ans.matrix[3][2] = (a.matrix[3][2] + b.matrix[3][2]) % mod;
		ans.matrix[3][3] = (a.matrix[3][3] + b.matrix[3][3]) % mod;
		return ans;
	}
	inline friend void operator+=(mat& a, const mat& b) {
		a = a + b;
	}
	inline static mat unite(int n) {
		mat I(n, n);
		I.matrix[0][0] = 1;
		I.matrix[1][1] = 1;
		I.matrix[2][2] = 1;
		I.matrix[3][3] = 1;
		return I;
	}
	inline friend mat operator*(const mat& a, const mat& b) {
		int n = a.n, m = a.m, s = b.m;
		mat ans(n, s);
		memset(ans.matrix, 0, sizeof(int) * 16);
		for (int i = 0; i < n; i++)
			for (int j = 0; j < s; j++) {
				ans.matrix[i][j] = (ans.matrix[i][j] + 1ll * a.matrix[i][0] * b.matrix[0][j] % mod) % mod;
				ans.matrix[i][j] = (ans.matrix[i][j] + 1ll * a.matrix[i][1] * b.matrix[1][j] % mod) % mod;
				ans.matrix[i][j] = (ans.matrix[i][j] + 1ll * a.matrix[i][2] * b.matrix[2][j] % mod) % mod;
				ans.matrix[i][j] = (ans.matrix[i][j] + 1ll * a.matrix[i][3] * b.matrix[3][j] % mod) % mod;
			}
		return ans;
	}
	inline friend void operator*=(mat& a, const mat b) {
		a = a * b;
	}
};
const int u = 1, v = 4;
#define ls (p << 1) + 1
#define rs (p << 1) + 2
struct node {
	mat elem, lazy;
	node(mat elem_ = mat(u, v)) {
		elem = elem_;
		lazy = mat::unite(v);
	}
};
vector<node> tree;
void init_t(int n) {
	n = (1 << ((int)ceil(log2(n)) + 1));
	tree.resize(n);
}
inline void push_down(int p) {
	tree[ls].elem *= tree[p].lazy;
	tree[ls].lazy *= tree[p].lazy;
	tree[rs].elem *= tree[p].lazy;
	tree[rs].lazy *= tree[p].lazy;
	tree[p].lazy = mat::unite(v);
}
inline void push_up(int p) {
	memset(tree[p].elem.matrix, 0, sizeof(int) * 16);
	tree[p].elem.n = u, tree[p].elem.m = v;
	tree[p].elem += tree[ls].elem;
	tree[p].elem += tree[rs].elem;
}
inline void multiply(const int& l, const int& r, const mat& x, const int& s, const int& t, int p) {
	if (s >= l && t <= r) {
		tree[p].elem *= x;
		tree[p].lazy *= x;
		return;
	}

	push_down(p);
	int m = (s + t) >> 1;
	if (m > l)
		multiply(l, r, x, s, m, ls);

	if (m < r)
		multiply(l, r, x, m, t, rs);

	push_up(p);
	return;
}
inline mat query(const int& l, const int& r, const int& s, const int& t, int p) {
	if (s >= l && t <= r) {
		return tree[p].elem;
	}

	push_down(p);
	int m = (s + t) >> 1;
	mat ans(u, v);

	if (m > l)
		ans += query(l, r, s, m, ls);

	if (m < r)
		ans += query(l, r, m, t, rs);

	return ans;
}
vector<mat> elemts;
inline void build(const int& s, const int& t, int p) {
	if (t - s == 1) {
		tree[p].elem = elemts[s];
		return;
	}

	int m = (s + t) >> 1;
	build(s, m, ls);
	build(m, t, rs);
	push_up(p);
}
mat k[7];
inline void init() {
	k[1].n = k[1].m = 4;
	k[1].matrix[0][0] = 1;
	k[1].matrix[1][0] = 1;
	k[1].matrix[1][1] = 1;
	k[1].matrix[2][2] = 1;
	k[1].matrix[3][3] = 1;
	k[2].n = k[2].m = 4;
	k[2].matrix[0][0] = 1;
	k[2].matrix[1][1] = 1;
	k[2].matrix[2][1] = 1;
	k[2].matrix[2][2] = 1;
	k[2].matrix[3][3] = 1;
	k[3].n = k[3].m = 4;
	k[3].matrix[0][0] = 1;
	k[3].matrix[0][2] = 1;
	k[3].matrix[1][1] = 1;
	k[3].matrix[2][2] = 1;
	k[3].matrix[3][3] = 1;
	k[4].n = k[4].m = 4;
	k[4].matrix[0][0] = 1;
	k[4].matrix[1][1] = 1;
	k[4].matrix[2][2] = 1;
	k[4].matrix[3][3] = 1;
	k[5].n = k[5].m = 4;
	k[5].matrix[0][0] = 1;
	k[5].matrix[2][2] = 1;
	k[5].matrix[3][3] = 1;
	k[6].n = k[6].m = 4;
	k[6].matrix[0][0] = 1;
	k[6].matrix[1][1] = 1;
	k[6].matrix[3][3] = 1;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	init();
	int n;
	cin >> n;
	elemts.resize(n, mat(u, v));

	for (int i = 0; i < n; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		elemts[i].matrix[0][0] = a;
		elemts[i].matrix[0][1] = b;
		elemts[i].matrix[0][2] = c;
		elemts[i].matrix[0][3] = 1;
	}
	int s = 0;
	init_t(n);
	build(s, n, 0);
	int m;
	cin >> m;
	int o, l, r, v;

	for (int i = 0; i < m; i++) {
		cin >> o >> l >> r;
		l--;

		if (o == 1)
			multiply(l, r, k[1], s, n, 0);
		else if (o == 2)
			multiply(l, r, k[2], s, n, 0);
		else if (o == 3)
			multiply(l, r, k[3], s, n, 0);
		else if (o == 4) {
			cin >> v;
			k[4].matrix[3][0] = v;
			multiply(l, r, k[4], s, n, 0);
		}
		else if (o == 5) {
			cin >> v;
			k[5].matrix[1][1] = v;
			multiply(l, r, k[5], s, n, 0);
		}
		else if (o == 6) {
			cin >> v;
			k[6].matrix[3][2] = v;
			multiply(l, r, k[6], s, n, 0);
		}
		else {
			mat ans = query(l, r, s, n, 0);
			cout << ans.matrix[0][0] << " " << ans.matrix[0][1] << " " << ans.matrix[0][2] << endl;
		}
	}
	return 0;
}

树上最大权独立集

如果不带修改的话就是 P1352 没有上司的舞会,众所周知,树上 DP 即可。

定义 f i 0 f_{i0} fi0 为不选 i i i 的时候以 i i i 为根的子树的可达最大值, f i 1 f_{i1} fi1 为选 i i i 的时候以 i i i 为根的子树的可达最大值。转移方程为 f i 1 = ∑ j ∈ i . s o n f j 0 + v i f i 0 = ∑ j ∈ i . s o n max ⁡ { f j 0 , f j 1 } f_{i1}=\sum_{j\in i.son} f_{j0}+v_i\\f_{i0}=\sum_{j\in i.son}\max\{f_{j0}, f_{j1}\} fi1=ji.sonfj0+vifi0=ji.sonmax{fj0,fj1}

那如果带修改呢?

既然修改那肯定得线段树,既然是树上线段树那肯定得树链剖分

先将每个点的 f f f 写成矩阵的形式

[ f i 0 f i 1 ] = [ f h 0 f h 1 ] × [ g i 0 g i 1 g i 0 − ∞ ] \left[\begin{matrix}f_{i0}&f_{i1}\end{matrix}\right]=\left[\begin{matrix}f_{h0}&f_{h1}\end{matrix}\right]\times\left[\begin{matrix}g_{i0}&g_{i1}\\g_{i0}&-\infty\end{matrix}\right] [fi0fi1]=[fh0fh1]×[gi0gi0gi1]

其中 h h h i i i 的重儿子 g i 0 = ∑ j ∈ i . s o n , j ≠ h max ⁡ { f j 0 , f j 1 } g i 1 = ( ∑ j ∈ i . s o n , j ≠ h f j 0 ) + v i g_{i0}=\sum_{j\in i.son,j\neq h}\max\{f_{j0}, f_{j1}\}\\g_{i1}=(\sum_{j\in i.son, j \neq h}f_{j0})+v_i gi0=ji.son,j=hmax{fj0,fj1}gi1=(ji.son,j=hfj0)+vi
矩阵乘法为广义。

树剖+线段树修改一下即可,注意修改时一定要算增加量。

const int maxn = 100005;
using vi = vector<int>;
struct mat {
	int n, m;
	int matrix[2][2];
	mat(int n_ = 2, int m_ = 2) {
		n = n_;
		m = m_;
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				matrix[i][j] = 0;
	}
	inline int* operator[](int i) {
		assert(i >= 0 && i < 2);
		return matrix[i];
	}
	inline friend mat operator^(mat a, mat b) {
		int n = a.n, m = b.m;
		assert(a.m == b.n);
		int s = a.m;
		mat res(n, m);
		for (int i =0 ; i < n; i++)
			for (int j = 0; j < m; j++) {
				res[i][j] = -INF;
				for (int k = 0; k < s; k++)
					res[i][j] = max(res[i][j], a[i][k] + b[k][j]);
			}
		return res;
	}
};
struct node {
	mat elem;
	node* l, * r;
	int s, t;
	node(int s_, int t_) {
		l = r = nullptr;
		s = s_; t = t_;
	}
	inline void pushup() {
		elem = l->elem ^ r->elem;
	}
};
inline void change(node* cur, const int& p, const mat& x) {
	if (!cur)
		return;
	if (cur->s == p && cur->t == p + 1) {
		cur->elem = x;
		return;
	}
	int m = cur->s + cur->t >> 1;
	if (p < m)
		change(cur->l, p, x);
	else
		change(cur->r, p, x);
	cur->pushup();
}
inline mat query(node* cur, const int& l, const int& r) {
	if (!cur)
		return mat(2, 2);
	if (cur->s >= l && cur->t <= r)
		return cur->elem;
	int m = cur->s + cur->t >> 1;
	if (m >= r)
		return query(cur->l, l, r);
	if (m <= l)
		return query(cur->r, l, r);
	else
		return query(cur->l, l, r) ^ query(cur->r, l, r);
}
vi edges[maxn];
vi son, fa, siz, dep;
vi a;
int f[maxn][2];
vector<mat> w;
inline void dfs1(int u, int ff, int d) {
	fa[u] = ff;
	siz[u] = 1;
	dep[u] = d;
	f[u][1] = a[u];
	int maxi = 0;
	for (int v : edges[u])
		if (v != ff) {
			dfs1(v, u, d + 1);
			siz[u] += siz[v];
			if (siz[v] > maxi)
				son[u] = v;
			maxi = max(maxi, siz[v]);
			f[u][1] += f[v][0];
			f[u][0] += max(f[v][0], f[v][1]);
		}
}
vi dfn, chain, ed, nod;
inline void dfs2(int u, int& cnt, bool c) {
	w[u][1][0] = a[u];
	w[u][1][1] = -INF;
	dfn[u] = cnt++;
	nod[dfn[u]] = u;
	if (c)
		chain[u] = chain[fa[u]];
	else
		chain[u] = u;
	ed[chain[u]] = u;
	if (son[u] == -1)
		return;
	dfs2(son[u], cnt, 1);
	for (int v : edges[u])
		if (v != fa[u] && v != son[u]) {
			dfs2(v, cnt, 0);
			w[u][0][0] += max(f[v][0], f[v][1]);
			w[u][1][0] += f[v][0];
		}
	w[u][0][1] = w[u][0][0];
}
inline node* build(int s, int t) {
	node* cur = new node(s, t);
	if (t - s == 1) {
		cur->elem = w[nod[s]];
		return cur;
	}
	int m = s + t >> 1;
	cur->l = build(s, m);
	cur->r = build(m, t);
	cur->pushup();
	return cur;
}
inline node* decomp(int n, int r) {
#define s(k) k.resize(n, -1)
	s(son), s(fa), s(siz);
	s(dfn), s(chain), s(nod);
	s(ed), s(dep);
#undef s
	dfs1(r, -1, 0);
	int cnt = 0;
	dfs2(r, cnt, 0);
	return build(0, n);
}
inline void update(node* rt, int u, int x) {
	w[u][1][0] += x - a[u];
	a[u] = x;
	while (true) {
		mat lst = query(rt, dfn[chain[u]], dfn[ed[chain[u]]] + 1);
		change(rt, dfn[u], w[u]);
		mat now = query(rt, dfn[chain[u]], dfn[ed[chain[u]]] + 1);
		u = fa[chain[u]];
		if (u == -1)
			break;
		w[u][0][0] += max(now[0][0], now[1][0]) - max(lst[0][0], lst[1][0]);
		w[u][0][1] = w[u][0][0];
		w[u][1][0] += now[0][0] - lst[0][0];
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	srand(time(0));
	int n, q;
	cin >> n >> q;
	a.resize(n);
	for (int i = 0; i < n; i++)
		cin >> a[i];
	for (int i = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		--u, --v;
		edges[u].push_back(v);
		edges[v].push_back(u);
	}
	w.resize(n);
	int r = 0;
	node* rt = decomp(n, r);
	while (q--) {
		int p, val;
		cin >> p >> val;
		--p;
		update(rt, p, val);
		mat res = query(rt, dfn[chain[r]], dfn[ed[chain[r]]] + 1);
		cout << max(res[0][0], res[1][0]) << endl;
	}
	return 0;
}

附:P5024 [NOIP2018 提高组] 保卫王国:最小权覆盖子集=全集-最大权独立子集

例:

P2886 [USACO07NOV]Cow Relays G

给定一张 T T T 条边的无向连通图,求从 S S S E E E 经过 N N N 条边的最短路长度。

1 ≤ N ≤ 10 , 2 ≤ T ≤ 100 , 1 ≤ u , v , w ≤ 1000 1\le N\le10, 2\le T\le 100, 1\le u, v, w\le1000 1N10,2T100,1u,v,w1000

解:

对点离散化,建立邻接矩阵,用矩阵广义乘法。

using vi = vector<int>;
struct mat {
	vector<vi> matrix;
	mat(vector<vi> m_) {
		matrix = m_;
	}
	mat(int n = 0, int m = 0) {
		matrix.resize(n, vi(m));
	}
	inline void resize(int n, int m) {
		matrix.resize(n, vi(m));
	}
	inline vi& operator[](int i) {
		return matrix[i];
	}
	inline friend mat operator+(mat a, mat b) {
		int n = a.size().first, m = a.size().second;
		mat ans(n, m);

		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				ans[i][j] = (a[i][j] + b[i][j]) % mod;

		return ans;
	}
	inline friend void operator+=(mat& a, mat b) {
		a = a + b;
	}
	inline static mat unite(int n) {
		mat I(n, n);

		for (int i = 0; i < n; i++)
			I[i][i] = 1;

		return I;
	}
	inline pair<int, int> size() {
		if (matrix.size() == 0)
			return { 0, 0 };

		return { matrix.size(), matrix[0].size() };
	}
	inline friend mat operator*(mat a, mat b) {
		int n = a.size().first, m = a.size().second, s = b.size().second;
		mat ans(n, s);

		for (int i = 0; i < n; i++)
			for (int j = 0; j < s; j++)
				for (int k = 0; k < m; k++)
					ans[i][j] = (ans[i][j] + a[i][k] * b[k][j]) % mod;

		return ans;
	}
	inline friend void operator*=(mat& a, mat b) {
		a = a * b;
	}
	inline friend mat operator~(mat k) {
		int n = k.size().first, m = k.size().second;
		mat ans(m, n);

		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				ans[i][j] = k[j][i];

		return ans;
	}
	inline friend mat operator^(mat a, mat b);
};
inline mat operator^(mat a, mat b) {
	int n = a.size().first, m = a.size().second, s = b.size().second;
	mat ans(n, s);
	for (int i = 0; i < n; i++)
		for (int j = 0; j < s; j++) {
			ans[i][j] = INF;
			for (int k = 0; k < m; k++)
				ans[i][j] = min(ans[i][j], a[i][k] + b[k][j]);
		}
	return ans;
}
mat pow(mat b, int p) {
	if (p == 0)
		return mat::unite(b.size().first);
	if (p == 1)
		return b;
	mat t = pow(b, p >> 1);
	t = t ^ t;
	if (p & 1)
		return t ^ b;
	return t;
}
struct edge {
	int u, v, w;
};
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	srand(time(0));
	int m, c, s, e;
	cin >> c >> m >> s >> e;
	map<int, int> mp;
	vector<edge> edges(m);
	int n = 0;
	for (int i = 0; i < m; i++) {
		int u, v, w;
		cin >> w >> u >> v;
		if (!mp.count(u))
			mp[u] = n++;
		if (!mp.count(v))
			mp[v] = n++;
		edges[i].u = mp[u];
		edges[i].v = mp[v];
		edges[i].w = w;
	}
	mat k(n, n);
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			k[i][j] = INF;
	for (int i = 0; i < m; i++) {
		int u = edges[i].u;
		int v = edges[i].v;
		int w = edges[i].w;
		k[u][v] = w;
		k[v][u] = w;
	}
	mat v(1, n);
	for (int i = 0; i < n; i++)
		v[0][i] = INF;
	v[0][mp[s]] = 0;
	cout << (v ^ pow(k, c))[0][mp[e]] << endl;
	return 0;
}

CF750E New Year and Old Subsequence

给定一个长为 n n n 的字符串,询问在区间 [ l , r ] [l,r] [l,r] 中,至少删除几个字符可以使得区间内包含子序列 2017 2017 2017,不包含子序列 2016 2016 2016

4 ≤ n ≤ 200000 , 1 ≤ q ≤ 200000 , 1 ≤ l ≤ r ≤ n 4\le n\le 200000,1\le q\le 200000,1\le l\le r\le n 4n200000,1q200000,1lrn

解:

f i , 0 / 1 / 2 / 3 / 4 f_{i,0/1/2/3/4} fi,0/1/2/3/4 代表字符串内恰好包含 ∅ / 2 / 20 / 201 / 2017 \emptyset/2/20/201/2017 ∅/2/20/201/2017 [ 1 , i ] [1,i] [1,i] 中最少要删除几个字符。

可以写出转移方程(有点不可以

f i , 0 = f i − 1 , 0 + [ s i = 2 ] f i , 1 = min ⁡ { f i − 1 , 1 + [ s i = 0 ] , f i − 1 , 0 + ( [ s i ≠ 2 ] ∞ ) } f i , 2 = min ⁡ { f i − 1 , 2 + [ s i = 1 ] , f i − 1 , 1 + ( [ s i ≠ 0 ] ∞ ) } f i , 3 = min ⁡ { f i − 1 , 3 + [ s i = 7 or ⁡ s i = 6 ] , f i − 1 , 2 + ( [ s i ≠ 1 ] ∞ ) } f i , 4 = min ⁡ { f i − 1 , 4 + [ s i = 6 ] , f i − 1 , 3 + ( [ s i ≠ 7 ] ∞ ) } f_{i,0}=f_{i-1,0}+[s_i=2]\\ f_{i,1}=\min\{f_{i-1,1}+[s_i=0],f_{i-1,0}+([s_i\ne2]\infty)\}\\ f_{i,2}=\min\{f_{i-1,2}+[s_i=1],f_{i-1,1}+([s_i\ne0]\infty)\}\\ f_{i,3}=\min\{f_{i-1,3}+[s_i=7 \operatorname{or} s_i=6],f_{i-1,2}+([s_i\ne1]\infty)\}\\ f_{i,4}=\min\{f_{i-1,4}+[s_i=6],f_{i-1,3}+([s_i\ne7]\infty)\} fi,0=fi1,0+[si=2]fi,1=min{fi1,1+[si=0],fi1,0+([si=2])}fi,2=min{fi1,2+[si=1],fi1,1+([si=0])}fi,3=min{fi1,3+[si=7orsi=6],fi1,2+([si=1])}fi,4=min{fi1,4+[si=6],fi1,3+([si=7])}

写成矩阵乘法的形式

[ [ c = 2 ] [ c ≠ 2 ] ∞ ∞ ∞ ∞ ∞ [ c = 0 ] [ c ≠ 0 ] ∞ ∞ ∞ ∞ ∞ [ c = 1 ] [ c ≠ 1 ] ∞ ∞ ∞ ∞ ∞ [ c = 7 ] + [ c = 6 ] [ c ≠ 7 ] ∞ ∞ ∞ ∞ ∞ [ c = 6 ] ] \left[ \begin{matrix} [c=2]&[c\ne2]\infty&\infty&\infty&\infty\\ \infty&[c=0]&[c\ne0]\infty&\infty&\infty\\ \infty&\infty&[c=1]&[c\ne1]\infty&\infty\\ \infty&\infty&\infty&[c=7]+[c=6]&[c\ne7]\infty\\ \infty&\infty&\infty&\infty&[c=6] \end{matrix} \right] [c=2][c=2][c=0][c=0][c=1][c=1][c=7]+[c=6][c=7][c=6]

线段树维护一下广义矩阵乘法即可。

using vi = vector<int>;
const int maxn = 5;
struct mat {
	int n, m;
	int c[maxn][maxn];
	mat(int n_ = 5, int m_ = 5) {
		n = n_;
		m = m_;
		memset(c, 0x3f, sizeof(c));
	}
	static mat unite(int n) {
		mat I(n);
		for (int i = 0; i < n; i++)
			I.c[i][i] = 1;
		return I;
	}
	friend mat operator*(const mat& a, const mat& b) {
		int n = a.n, m = b.m;
		assert(a.m == b.n);
		int s = a.m;
		mat ans(n, m);
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++) {
				ans.c[i][j] = INF;
				for (int k = 0; k < s; k++)
					ans.c[i][j] = min(ans.c[i][j], a.c[i][k] + b.c[k][j]);
			}
		return ans;
	}
};
struct node {
	mat elem;
	node* l, * r;
	int s, t;
	node(int s_, int t_) {
		s = s_; t = t_;
		l = r = nullptr;
		elem = mat(5, 5);
	}
	inline void pushup() {
		elem = l->elem * r->elem;
	}
};
mat conv(char c) {
	mat res(5, 5);
	for (int i = 0; i < 5; i++)
		for (int j = 0; j < 5; j++)
			res.c[i][j] = INF;
	res.c[0][0] = (c == '2');
	res.c[0][1] = (c != '2') * INF;
	res.c[1][1] = (c == '0');
	res.c[1][2] = (c != '0') * INF;
	res.c[2][2] = (c == '1');
	res.c[2][3] = (c != '1') * INF;
	res.c[3][3] = (c == '6') | (c == '7');
	res.c[3][4] = (c != '7') * INF;
	res.c[4][4] = (c == '6');
	return res;
}
vector<char> st;
node* build(int s, int t) {
	node* cur = new node(s, t);
	if (t - s == 1) {
		cur->elem = conv(st[s]);
		return cur;
	}
	int m = s + t >> 1;
	cur->l = build(s, m);
	cur->r = build(m, t);
	cur->pushup();
	return cur;
}
mat query(node* cur, int l, int r) {
	if (!cur)
		return mat(5, 5);
	if (cur->s >= l && cur->t <= r)
		return cur->elem;
	int m = cur->s + cur->t >> 1;
	if (m >= r)
		return query(cur->l, l, r);
	if (m <= l)
		return query(cur->r, l, r);
	return query(cur->l, l, r) * query(cur->r, l, r);
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	srand(time(0));
	int n, q;
	cin >> n >> q;
	st.resize(n);
	for (int i = 0; i < n; i++)
		cin >> st[i];
	node* rt = build(0, n);
	while (q--) {
		int l, r;
		cin >> l >> r;
		--l;
		int ans = query(rt, l, r).c[0][4];
		if (ans != INF)
			cout << ans << endl;
		else
			cout << "-1" << endl;
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一篇关于区间DP的学习笔记,希望对你有所帮助。 ### 什么是区间DP 区间 DP 是一种动态规划算法,用于解决一些区间上的问题。具体来说,区间 DP 通常用于解决如下问题: - 最长公共子序列(LCS) - 最长递增子序列(LIS) - 最大子段和 - 区间选数问题 区间 DP 通常采用分治或递推的方式进行求解,具体方法取决于问题的性质。 ### 区间 DP 的递推方法 区间 DP 的递推方法通常有两种,一种是自底向上的递推方法,一种是自顶向下的记忆化搜索方法。 自底向上的递推方法通常采用二维数组或三维数组来记录状态转移方程,具体的递推方式如下: ```cpp for (int len = 2; len <= n; len++) { for (int i = 1; i <= n - len + 1; i++) { int j = i + len - 1; for (int k = i; k < j; k++) { // 状态转移方程 } } } ``` 其中,len 表示区间长度,i 和 j 分别表示区间的左右端点,k 表示区间的划分点。 自顶向下的记忆化搜索方法通常采用记忆化数组来记录状态转移方程,具体的递推方式如下: ```cpp int dp(int i, int j) { if (i == j) return 0; if (memo[i][j] != -1) return memo[i][j]; memo[i][j] = INF; for (int k = i; k < j; k++) { memo[i][j] = min(memo[i][j], dp(i, k) + dp(k + 1, j) + ...); } return memo[i][j]; } ``` 其中,i 和 j 分别表示区间的左右端点,k 表示区间的划分点,memo 数组用于记忆化状态转移方程。 ### 区间 DP 的优化 对于一些区间 DP 问题,我们可以通过一些技巧和优化来减少时间和空间的消耗。 一种常见的优化方式是状态压缩,将二维或三维数组压缩成一维数组,从而减少空间的消耗。 另一种常见的优化方式是使用滚动数组,将数组的维度从二维或三维减少到一维,从而减少时间和空间的消耗。 此外,对于一些具有特殊性质的区间 DP 问题,我们还可以使用单调队列或单调栈等数据结构来进行优化,从而减少时间和空间的消耗。 ### 总结 区间 DP 是一种常用的动态规划算法,用于解决一些区间上的问题。区间 DP 通常采用分治或递推的方式进行求解,具体方法取决于问题的性质。对于一些区间 DP 问题,我们可以通过一些技巧和优化来减少时间和空间的消耗。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值