动态 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= a11a21am1a12a22…am2…………a1na2namn
其中 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=1∑daik×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=1∑d2(k1=1∑d1aik1×bk1k2)×ck2j=k2=1∑d2k1=1∑d1aik1×bk1k2×ck2j=k1=1∑d1aik1×k2=1∑d2(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(x−1)+f(x−2),我们可以将其转化为矩阵形式,即:
[ 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(x−1)]=[f(x−1)f(x−2)]×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(x−1)+f(x−2) 可得
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(x−1)+c×f(x−2)=f(x)b×f(x−1)+d×f(x−2)=f(x−1)
最终解得 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,有几种操作:
- 在 [ l , r ] [l,r] [l,r] 区间内令 A i = A i + B i A_i=A_i+B_i Ai=Ai+Bi。
- 在 [ l , r ] [l,r] [l,r] 区间内令 B i = B i + C i B_i=B_i+C_i Bi=Bi+Ci。
- 在 [ l , r ] [l,r] [l,r] 区间内令 C i = C i + A i C_i=C_i+A_i Ci=Ci+Ai。
- 在 [ l , r ] [l,r] [l,r] 区间内令 A i = A i + v A_i=A_i+v Ai=Ai+v。
- 在 [ l , r ] [l,r] [l,r] 区间内令 B i = B i × v B_i=B_i\times v Bi=Bi×v。
- 在 [ l , r ] [l,r] [l,r] 区间内令 C i = v C_i=v Ci=v。
- 询问区间每个属性的和。
答案对
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,m≤2.5×105,0≤Ai,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=j∈i.son∑fj0+vifi0=j∈i.son∑max{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=j∈i.son,j=h∑max{fj0,fj1}gi1=(j∈i.son,j=h∑fj0)+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 1≤N≤10,2≤T≤100,1≤u,v,w≤1000
解:
对点离散化,建立邻接矩阵,用矩阵广义乘法。
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 4≤n≤200000,1≤q≤200000,1≤l≤r≤n
解:
令 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=fi−1,0+[si=2]fi,1=min{fi−1,1+[si=0],fi−1,0+([si=2]∞)}fi,2=min{fi−1,2+[si=1],fi−1,1+([si=0]∞)}fi,3=min{fi−1,3+[si=7orsi=6],fi−1,2+([si=1]∞)}fi,4=min{fi−1,4+[si=6],fi−1,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;
}