动态 DP 简称 DDP,一说是带修改操作的 DP,一说是将 DP 的一个维度作为时间轴以动态求解 DP。
——让我们回归本质,将一切线性操作归为矩阵。
既然(狭义的)线性 DP 的转移方程是线性变换,就必然可以用矩阵表示。
基础矩阵乘法和快速幂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using  Matrix = vector<vector<int >>;constexpr  Matrix operator  * (const  Matrix& A, const  Matrix& B) {    int  n = A.size ();     Matrix C (n, vector<int >(n))  ;     for  (int  k = 0 ; k < n; k++) { 	    for  (int  i = 0 ; i < n; i++) { 	        for  (int  j = 0 ; j < n; j++) {                 C[i][j] += A[i][k] * B[k][j];             }	         }	     }	     return  C; } constexpr  Matrix qpow (Matrix A, int  k)      int  n = A.size ();     Matrix B (n, vector<int >(n))  ;     for  (int  i = 0 ; i < n; i++) B[i][i] = 1 ;     for  (; k; k >>= 1 ) {         if  (k & 1 ) B = B * A;         A = A * A;     }     return  B; } 
给定长度为N N N A = ( A 1 , A 2 , … , A N ) A = (A_{1}, A_{2}, \dots, A_{N}) A = ( A 1  , A 2  , … , A N  ) N ⩽ 2 × 1 0 5 N \leqslant 2 \times 10^{5} N ⩽ 2 × 1 0 5 
枚举最小值A i A_{i} A i  A i A_{i} A i  A j A_{j} A j  A j ⩾ A i A_{j} \geqslant A_{i} A j  ⩾ A i  
下面先讨论最小值为A i A_{i} A i  
设状态f k , 0 / 1 , 0 / 1 f_{k,0/1,0/1} f k , 0 / 1 , 0 / 1  k k k [ 1 , k ] [1,k] [ 1 , k ] 
初始状态:f 0 = ( 0 , 0 , 0 , 0 ) T f_{0} = (0, 0, 0, 0)^{\mathrm T} f 0  = ( 0 , 0 , 0 , 0 ) T 
状态转移:如果A k ⩾ A i A_{k} \geqslant A_{i} A k  ⩾ A i  
{ f k , 0 , 0 = max  ( f k − 1 , 0 , 0 , f k − 1 , 0 , 1 ) f k , 0 , 1 = f k − 1 , 0 , 0 + 1 f k , 1 , 0 = max  ( f k − 1 , 1 , 0 , f k − 1 , 1 , 1 ) f k , 1 , 1 = max  ( f k − 1 , 0 , 0 + A k + 1 , f k − 1 , 1 , 0 + 1 ) \begin{cases} f_{k,0,0} = \max(f_{k-1,0,0}, f_{k-1,0,1}) \\ f_{k,0,1} = f_{k-1,0,0} + 1 \\ f_{k,1,0} = \max(f_{k-1,1,0}, f_{k-1,1,1}) \\ f_{k,1,1} = \max(f_{k-1,0,0} + A_k+1, f_{k-1,1,0} + 1) \end{cases} ⎩ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎧  f k , 0 , 0  = max ( f k − 1 , 0 , 0  , f k − 1 , 0 , 1  ) f k , 0 , 1  = f k − 1 , 0 , 0  + 1 f k , 1 , 0  = max ( f k − 1 , 1 , 0  , f k − 1 , 1 , 1  ) f k , 1 , 1  = max ( f k − 1 , 0 , 0  + A k  + 1 , f k − 1 , 1 , 0  + 1 )  
否则不允许被染红,
{ f k , 0 , 0 = max  ( f k − 1 , 0 , 0 , f k − 1 , 0 , 1 ) f k , 0 , 1 = − ∞ f k , 1 , 0 = max  ( f k − 1 , 1 , 0 , f k − 1 , 1 , 1 ) f k , 1 , 1 = − ∞ \begin{cases} f_{k,0,0} = \max(f_{k-1,0,0}, f_{k-1,0,1}) \\ f_{k,0,1} = -\infty \\ f_{k,1,0} = \max(f_{k-1,1,0}, f_{k-1,1,1}) \\ f_{k,1,1} = -\infty \end{cases} ⎩ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎧  f k , 0 , 0  = max ( f k − 1 , 0 , 0  , f k − 1 , 0 , 1  ) f k , 0 , 1  = − ∞ f k , 1 , 0  = max ( f k − 1 , 1 , 0  , f k − 1 , 1 , 1  ) f k , 1 , 1  = − ∞  
欲求max  { f N , 0 / 1 , 1 + A i } \max\lbrace f_{N, 0 / 1, 1} + A_{i} \rbrace max { f N , 0 / 1 , 1  + A i  } 
上面的写法有点丑,不容易看出本质(尤其是结合律),可以采用矩阵以更清晰地表示。
标准的矩阵乘法是
( A ⋅ B ) i j = ∑ k = 1 N ( A i k × B k j ) (\boldsymbol{A} \cdot \boldsymbol{B})_{i j} = \sum_{k = 1}^{N} (\boldsymbol{A}_{i k} \times \boldsymbol{B}_{k j}) ( A ⋅ B ) i j  = k = 1 ∑ N  ( A i k  × B k j  ) 
为了计算 DP 最小值,将 加法  (∑ \sum ∑ 取最小值  (min),乘法  (× \times × 普通加法  (+),得到一种新的矩阵乘法规则
( A ⋅ B ) i j = min  k = 1 N ( A i k + B k j ) (\boldsymbol{A} \cdot \boldsymbol{B})_{i j} = \min_{k = 1}^{N} (\boldsymbol{A}_{i k} + \boldsymbol{B}_{k j}) ( A ⋅ B ) i j  = k = 1 min N  ( A i k  + B k j  ) 
注意min  \min min + ∞ +\infty + ∞ I \boldsymbol{I} I + ∞ +\infty + ∞ 
你会发现代码和 Floyd 一模一样。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using  Matrix = vector<vector<int >>;constexpr  Matrix operator  * (const  Matrix& A, const  Matrix& B) {    const  int  n = A.size ();     Matrix C (n, vector<int >(n, inf))  ;     for  (int  k = 0 ; k < n; k++) {         for  (int  i = 0 ; i < n; i++) {             for  (int  j = 0 ; j < n; j++) {                 C[i][j] = min (C[i][j], A[i][k] + B[k][j]);             }         }     }     return  C; } constexpr  Matrix qpow (Matrix A, int  k)      const  int  n = A.size ();     Matrix B (n, vector<int >(n, inf))  ;     for  (int  i = 0 ; i < n; i++) B[i][i] = 0 ;     for  (; k; k >>= 1 ) {         if  (k & 1 ) B = B * A;         A = A * A;     }     return  B; } 
计算 DP 最大值同理。
为什么这种规则下仍满足矩阵的性质? 
这里,加法+ + + × \times × + + + × \times × + + + × \times × + + +  
「热带 (Tropical)」这个名字据说是为了纪念巴西数学家 Imre Simon,他是这个领域的先驱之一,而巴西地处热带。这个命名并没有数学上的直接含义,更多的是一种致敬。 
热带半环是半环的一种具体实例,为许多图论算法和 DP 问题提供了一个自然的代数框架,尤其是 Min-Plus 半环。从上面的例子也能看出,其运算规则恰好能描述这些问题中的状态转移和优化过程。 
回到本题,如果A k ⩾ A i A_{k} \geqslant A_{i} A k  ⩾ A i  
[ f k , 0 , 0 f k , 0 , 1 f k , 1 , 0 f k , 1 , 1 ] = [ 0 0 − ∞ − ∞ 1 − ∞ − ∞ − ∞ − ∞ − ∞ 0 0 A k + 1 − ∞ 1 − ∞ ] ⊗ [ f k − 1 , 0 , 0 f k − 1 , 0 , 1 f k − 1 , 1 , 0 f k − 1 , 1 , 1 ] \begin{bmatrix} f_{k,0,0} \\ f_{k,0,1} \\ f_{k,1,0} \\ f_{k,1,1} \end{bmatrix} = \begin{bmatrix} 0 & 0 & -\infty & -\infty \\ 1 & -\infty & -\infty & -\infty \\ -\infty & -\infty & 0 & 0 \\ A_k+1 & -\infty & 1 & -\infty \end{bmatrix} \otimes \begin{bmatrix} f_{k-1,0,0} \\ f_{k-1,0,1} \\ f_{k-1,1,0} \\ f_{k-1,1,1} \end{bmatrix} ⎣ ⎢ ⎢ ⎢ ⎡  f k , 0 , 0  f k , 0 , 1  f k , 1 , 0  f k , 1 , 1   ⎦ ⎥ ⎥ ⎥ ⎤  = ⎣ ⎢ ⎢ ⎢ ⎡  0 1 − ∞ A k  + 1  0 − ∞ − ∞ − ∞  − ∞ − ∞ 0 1  − ∞ − ∞ 0 − ∞  ⎦ ⎥ ⎥ ⎥ ⎤  ⊗ ⎣ ⎢ ⎢ ⎢ ⎡  f k − 1 , 0 , 0  f k − 1 , 0 , 1  f k − 1 , 1 , 0  f k − 1 , 1 , 1   ⎦ ⎥ ⎥ ⎥ ⎤  
否则,
[ f k , 0 , 0 f k , 0 , 1 f k , 1 , 0 f k , 1 , 1 ] = [ 0 0 − ∞ − ∞ − ∞ − ∞ − ∞ − ∞ − ∞ − ∞ 0 0 − ∞ − ∞ − ∞ − ∞ ] ⊗ [ f k − 1 , 0 , 0 f k − 1 , 0 , 1 f k − 1 , 1 , 0 f k − 1 , 1 , 1 ] \begin{bmatrix} f_{k,0,0} \\ f_{k,0,1} \\ f_{k,1,0} \\ f_{k,1,1} \end{bmatrix} = \begin{bmatrix} 0 & 0 & -\infty & -\infty \\ -\infty & -\infty & -\infty & -\infty \\ -\infty & -\infty & 0 & 0 \\ -\infty & -\infty & -\infty & -\infty \end{bmatrix} \otimes \begin{bmatrix} f_{k-1,0,0} \\ f_{k-1,0,1} \\ f_{k-1,1,0} \\ f_{k-1,1,1} \end{bmatrix} ⎣ ⎢ ⎢ ⎢ ⎡  f k , 0 , 0  f k , 0 , 1  f k , 1 , 0  f k , 1 , 1   ⎦ ⎥ ⎥ ⎥ ⎤  = ⎣ ⎢ ⎢ ⎢ ⎡  0 − ∞ − ∞ − ∞  0 − ∞ − ∞ − ∞  − ∞ − ∞ 0 − ∞  − ∞ − ∞ 0 − ∞  ⎦ ⎥ ⎥ ⎥ ⎤  ⊗ ⎣ ⎢ ⎢ ⎢ ⎡  f k − 1 , 0 , 0  f k − 1 , 0 , 1  f k − 1 , 1 , 0  f k − 1 , 1 , 1   ⎦ ⎥ ⎥ ⎥ ⎤  
复杂度O ( N K 3 ) \mathcal{O}(N K^{3}) O ( N K 3 ) K = 4 K=4 K = 4 
对于不同的最小值A i A_{i} A i  O ( K 3 N log  N ) \mathcal{O}(K^{3} N \log N) O ( K 3 N log  N ) K = 4 K=4 K = 4 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 constexpr  ll inf = 0x3f3f3f3f3f3f3f3f ;struct  Info  : array<array<ll, 4>, 4> {	Info () { 		for  (int  i = 0 ; i < 4 ; i++) { 			for  (int  j = 0 ; j < 4 ; j++) { 				(*this )[i][j] = (i == j ? 0  : -inf); 			} 		} 	} }; Info operator  + (const  Info& a, const  Info& b) { 	Info c; 	for  (int  i = 0 ; i < 4 ; i++) { 		for  (int  j = 0 ; j < 4 ; j++) { 			c[i][j] = -inf; 			for  (int  k = 0 ; k < 4 ; k++) { 				c[i][j] = max (c[i][j], a[k][j] + b[i][k]); 			} 		} 	} 	return  c; } void  solve ()  	int  n; 	cin >> n; 	vector<int > A (n)  ; 	for  (int  i = 0 ; i < n; i++) { 		cin >> A[i]; 	} 	vector<int > ord (n)  ; 	iota (ord.begin (), ord.end (), 0 ); 	sort (ord.begin (), ord.end (), [&](int  i, int  j) { 		return  A[i] > A[j]; 	}); 	Info M0; 	for  (int  i = 0 ; i < 4 ; i++) { 		for  (int  j = 0 ; j < 4 ; j++) { 			M0[i][j] = -inf; 		} 	} 	M0[0 ][0 ] = 0 ; 	M0[0 ][1 ] = 0 ; 	M0[2 ][2 ] = 0 ; 	M0[2 ][3 ] = 0 ; 	SegmentTree<Info> seg (n)  ; 	for  (int  i = 0 ; i < n; i++) { 		seg.update (i, M0); 	} 	ll ans = 0 ; 	for  (auto  i : ord) { 		Info M1 = M0; 		M1[1 ][0 ] = 1 ; 		M1[3 ][0 ] = A[i] + 1 ; 		M1[3 ][2 ] = 1 ; 		seg.update (i, M1); 		auto  res = seg.query (0 , n); 		ans = max (ans, max (res[2 ][0 ], res[3 ][0 ]) + A[i]); 	} 	cout << ans << endl; } 
给定一棵N N N Q Q Q x x x y y y 
首先考虑没有修改操作的静态版本。设 f u , 0 / 1 f_{u, 0 / 1} f u , 0 / 1  u u u u u u 
{ f u , 0 = ∑ v ∈ children  ( u ) max  ( f v , 0 , f v , 1 )   f u , 1 = w u + ∑ v ∈ children  ( u ) f v , 0 \begin{cases} f_{u, 0} = \sum_{v \in \operatorname{children}(u)} \max(f_{v, 0}, f_{v, 1})  \\ f_{u, 1} = w_{u} + \sum_{v \in \operatorname{children}(u)} f_{v, 0} \end{cases} { f u , 0  = ∑ v ∈ c h i l d r e n ( u )  max ( f v , 0  , f v , 1  )   f u , 1  = w u  + ∑ v ∈ c h i l d r e n ( u )  f v , 0   
答案是max  ( f root , 0 , f root , 1 ) \max(f_{\text{root}, 0}, f_{\text{root}, 1}) max ( f root , 0  , f root , 1  ) 
为了优化修改操作,引入重链剖分,并将 DP 方程根据重儿子 (heavy child) 和轻儿子 (light children) 进行拆分。设 g u , 0 / 1 g_{u, 0 / 1} g u , 0 / 1  u u u 
{ g u , 0 = ∑ v ∈ lightchildren  ( u ) max  ( f v , 0 , f v , 1 )   g u , 1 = w u + ∑ v ∈ lightchildren  ( u ) f v , 0 \begin{cases} g_{u, 0} = \sum_{v \in \operatorname{lightchildren}(u)} \max(f_{v, 0}, f_{v, 1})  \\ g_{u, 1} = w_{u} + \sum_{v \in \operatorname{lightchildren}(u)} f_{v, 0} \end{cases} { g u , 0  = ∑ v ∈ l i g h t c h i l d r e n ( u )  max ( f v , 0  , f v , 1  )   g u , 1  = w u  + ∑ v ∈ l i g h t c h i l d r e n ( u )  f v , 0   
这样,
{ f u , 0 = g u , 0 + max  ( f heavychild  ( u ) , 0 , f heavychild  ( u ) , 1 )   f u , 1 = g u , 1 + f heavychild  ( u ) , 0 \begin{cases} f_{u, 0} = g_{u, 0} + \max(f_{\operatorname{heavychild}(u), 0}, f_{\operatorname{heavychild}(u), 1})  \\ f_{u, 1} = g_{u, 1} + f_{\operatorname{heavychild}(u), 0} \end{cases} { f u , 0  = g u , 0  + max ( f h e a v y c h i l d ( u ) , 0  , f h e a v y c h i l d ( u ) , 1  )   f u , 1  = g u , 1  + f h e a v y c h i l d ( u ) , 0   
这个 DP 方程可以用 Max-Plus 规则下的矩阵表示为
[ f u , 0 f u , 1 ] = [ g u , 0 g u , 0 g u , 1 − ∞ ] ⊗ [ f heavychild  ( u ) , 0 f heavychild  ( u ) , 1 ] \begin{bmatrix} f_{u, 0} \\ f_{u, 1} \end{bmatrix} = \begin{bmatrix} g_{u, 0} & g_{u, 0} \\ g_{u, 1} & -\infty \end{bmatrix} \otimes \begin{bmatrix} f_{\operatorname{heavychild}(u), 0} \\ f_{\operatorname{heavychild}(u), 1} \end{bmatrix} [ f u , 0  f u , 1   ] = [ g u , 0  g u , 1   g u , 0  − ∞  ] ⊗ [ f h e a v y c h i l d ( u ) , 0  f h e a v y c h i l d ( u ) , 1   ] 
也就是说,每个节点u u u 2 × 2 2 \times 2 2 × 2 
M u = [ g u , 0 g u , 0 g u , 1 − ∞ ] \boldsymbol{M}_u = \begin{bmatrix} g_{u, 0} & g_{u, 0} \\ g_{u, 1} & -\infty \end{bmatrix} M u  = [ g u , 0  g u , 1   g u , 0  − ∞  ] 
于是
f u = M u ⊗ f heavychild  ( u ) \boldsymbol{f}_{u} = \boldsymbol{M}_{u} \otimes \boldsymbol{f}_{\operatorname{heavychild}(u)} f u  = M u  ⊗ f h e a v y c h i l d ( u )  
考虑一条重链,设这重链为u 1 → u 2 → ⋯ → u k u_1 \to u_2 \to \dots \to u_k u 1  → u 2  → ⋯ → u k  u k u_k u k  [ 0 − ∞ ] \begin{bmatrix} 0 \\ -\infty \end{bmatrix} [ 0 − ∞  ] 
在这条链上,转移方程是这样的:
{ f u 1 = M u 1 ⊗ f u 2 f u 2 = M u 2 ⊗ f u 3 ⋯ f u k − 1 = M u k − 1 ⊗ f u k f u k = M u k ⊗ [ 0 − ∞ ]    ⟹    f u 1 = ( M u 1 ⊗ M u 2 ⊗ ⋯ ⊗ M u k ) ⊗ [ 0 − ∞ ] \begin{cases} \boldsymbol{f}_{u_{1}} = \boldsymbol{M}_{u_{1}} \otimes \boldsymbol{f}_{u_{2}} \\ \boldsymbol{f}_{u_{2}} = \boldsymbol{M}_{u_{2}} \otimes \boldsymbol{f}_{u_{3}} \\ \cdots \\ \boldsymbol{f}_{u_{k-1}} = \boldsymbol{M}_{u_{k-1}} \otimes \boldsymbol{f}_{u_{k}} \\ \boldsymbol{f}_{u_{k}} = \boldsymbol{M}_{u_{k}} \otimes \begin{bmatrix} 0 \\ -\infty \end{bmatrix} \end{cases} \implies \boldsymbol{f}_{u_1} = (\boldsymbol{M}_{u_1} \otimes \boldsymbol{M}_{u_2} \otimes \dots \otimes \boldsymbol{M}_{u_k}) \otimes \begin{bmatrix} 0 \\ -\infty \end{bmatrix} ⎩ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎧  f u 1   = M u 1   ⊗ f u 2   f u 2   = M u 2   ⊗ f u 3   ⋯ f u k − 1   = M u k − 1   ⊗ f u k   f u k   = M u k   ⊗ [ 0 − ∞  ]  ⟹ f u 1   = ( M u 1   ⊗ M u 2   ⊗ ⋯ ⊗ M u k   ) ⊗ [ 0 − ∞  ] 
这意味着,一条重链上所有矩阵的乘积包含了计算该链头最终 DP 值所需的所有信息,可以使用线段树维护区间信息。
因此,节点u u u    ⟹    \implies ⟹ M u \boldsymbol{M}_{u} M u     ⟹    \implies ⟹    ⟹    \implies ⟹ f top  ( u ) \boldsymbol{f}_{\operatorname{top}(u)} f t o p ( u )  
这个变化直接(通过轻边)影响了父节点parent  ( top  ( u ) ) \operatorname{parent}(\operatorname{top}(u)) p a r e n t ( t o p ( u ) ) g g g 
{ g parent  ( top  ( u ) ) , 0 ← g parent  ( top  ( u ) ) , 0 ′ − max  ( f top  ( u ) , 0 ′ , f top  ( u ) , 1 ′ ) + max  ( f top  ( u ) , 0 , f top  ( u ) , 1 ) g parent  ( top  ( u ) ) , 1 ← g parent  ( top  ( u ) ) , 1 ′ − f top  ( u ) , 0 ′ + f top  ( u ) , 0 \begin{cases} g_{\operatorname{parent}(\operatorname{top}(u)), 0} \gets g'_{\operatorname{parent}(\operatorname{top}(u)), 0} - \max(f'_{\operatorname{top}(u), 0}, f'_{\operatorname{top}(u), 1}) + \max(f_{\operatorname{top}(u), 0}, f_{\operatorname{top}(u), 1}) \\ g_{\operatorname{parent}(\operatorname{top}(u)), 1} \gets g'_{\operatorname{parent}(\operatorname{top}(u)), 1} - f'_{\operatorname{top}(u), 0} + f_{\operatorname{top}(u), 0} \end{cases} { g p a r e n t ( t o p ( u ) ) , 0  ← g p a r e n t ( t o p ( u ) ) , 0 ′  − max ( f t o p ( u ) , 0 ′  , f t o p ( u ) , 1 ′  ) + max ( f t o p ( u ) , 0  , f t o p ( u ) , 1  ) g p a r e n t ( t o p ( u ) ) , 1  ← g p a r e n t ( t o p ( u ) ) , 1 ′  − f t o p ( u ) , 0 ′  + f t o p ( u ) , 0   
这个公式的含义是新的g g g g g g 
当g p g_p g p  p p p M p \boldsymbol{M}_p M p  parent  ( top  ( u ) ) \operatorname{parent}(\operatorname{top}(u)) p a r e n t ( t o p ( u ) ) 
整个流程写完整是:节点u u u    ⟹    \implies ⟹ M u \boldsymbol{M}_{u} M u     ⟹    \implies ⟹    ⟹    \implies ⟹ f top  ( u ) \boldsymbol{f}_{\operatorname{top}(u)} f t o p ( u )     ⟹    \implies ⟹ g parent  ( top  ( u ) ) , M parent  ( top  ( u ) ) \boldsymbol{g}_{\operatorname{parent}(\operatorname{top}(u))},\boldsymbol{M}_{\operatorname{parent}(\operatorname{top}(u))} g p a r e n t ( t o p ( u ) )  , M p a r e n t ( t o p ( u ) )     ⟹    \implies ⟹    ⟹    \implies ⟹    ⟹    \implies ⟹    ⟹    ⋯    ⟹    \implies \cdots \implies ⟹ ⋯ ⟹ 
由于重链剖分保证任意点到根的路径为O ( log  N ) \mathcal{O}(\log N) O ( log  N ) O ( log  N ) \mathcal{O}(\log N) O ( log  N ) O ( log  2 N ) \mathcal{O}(\log^{2} N) O ( log  2 N ) 
总复杂度O ( N log  N + Q log  2 N ) \mathcal{O}(N\log N + Q \log^{2} N) O ( N log  N + Q log  2 N ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 int  main ()      int  N, Q;     cin >> N >> Q;     vector<int > A (N)  ;     for  (int  i = 0 ; i < N; i++) {         cin >> A[i];     }          for  (int  i = 1 ; i < N; i++) {         int  x, y;         cin >> x >> y;         x--; y--;         adj[x].push_back (y);         adj[y].push_back (x);     }     work (0 );     vector<array<int , 2>> f (N), g (N);     for  (auto  x : vector (ord.rbegin (), ord.rend ())) {         f[x][0 ] = 0 ;         f[x][1 ] = g[x][1 ] = A[x];         for  (auto  y : adj[x]) {             f[x][0 ] += max (f[y][0 ], f[y][1 ]);             f[x][1 ] += f[y][0 ];             if  (y != adj[x][0 ]) {                 g[x][0 ] += max (f[y][0 ], f[y][1 ]);                 g[x][1 ] += f[y][0 ];             }         }     }     SegmentTree<Matrix> seg (N)  ;     for  (int  x = 0 ; x < N; x++) {         seg.apply (dfn[x], Matrix (g[x][0 ], g[x][0 ], g[x][1 ], -inf));     }     while  (Q--) {         int  x, w;         cin >> x >> w;         x--;                  g[x][1 ] += w - A[x];         A[x] = w;         while  (x != -1 ) {             Matrix M = seg.query (dfn[top[x]], end[top[x]]);             if  (parent[top[x]] != -1 ) {                 g[parent[top[x]]][0 ] -= max (M[0 ][0 ], M[1 ][0 ]);                 g[parent[top[x]]][1 ] -= M[0 ][0 ];             }                          seg.apply (dfn[x], Matrix (g[x][0 ], g[x][0 ], g[x][1 ], -inf));                          M = seg.query (dfn[top[x]], end[top[x]]);             if  (parent[top[x]] != -1 ) {                 g[parent[top[x]]][0 ] += max (M[0 ][0 ], M[1 ][0 ]);                 g[parent[top[x]]][1 ] += M[0 ][0 ];             }             x = parent[top[x]];         }                  Matrix res = seg.query (dfn[0 ], end[0 ]);         cout << max (res[0 ][0 ], res[1 ][0 ]) << endl;     }     return  0 ; } 
[[…/ 图 GRAPH/2 最短路 OPTIMAL PATHS#Bellman-Ford, Floyd-Warshall (DP)|Link]]
此题被 ACwing 收录,大概是最最经典的问题。
The 3rd Universal Cup. Stage 13: Sendai M. Do Not Turn Back 
无向图,问1 1 1 N N N K K K N ⩽ 100 , K ⩽ 1 0 9 N \leqslant 100,K \leqslant 10^{9} N ⩽ 1 0 0 , K ⩽ 1 0 9 
设f k , v f_{k,v} f k , v  k k k v v v 
f k , v = ∑ u → v f k − 1 , u − ( deg  ( v ) − 1 ) ⋅ f k − 2 , v f_{k,v} = \sum_{u \to v} f_{k-1,u} - (\operatorname{deg}(v)-1) \cdot f_{k-2,v} f k , v  = u → v ∑  f k − 1 , u  − ( d e g ( v ) − 1 ) ⋅ f k − 2 , v  
即
{ F k = E ⋅ F k − 1 − ( D − I ) F k − 2 F 2 = E ⋅ F k − 1 − D F 0 F 1 = E F 0 = I \begin{cases} F_{k} = E \cdot F_{k-1} - (D - I) F_{k-2} \\ F_{2} = E \cdot F_{k-1} - D F_{0} \\ F_{1} = E \\ F_{0} = I \end{cases} ⎩ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎧  F k  = E ⋅ F k − 1  − ( D − I ) F k − 2  F 2  = E ⋅ F k − 1  − D F 0  F 1  = E F 0  = I  
这可以写成矩阵乘法的形式:
( F k F k − 1 ) = ( E I − D I 0 ) ⋅ ( F k − 1 F k − 2 ) \begin{pmatrix} F_k \\ F_{k-1} \end{pmatrix} = \begin{pmatrix} E & I-D \\ I & 0 \end{pmatrix} \cdot \begin{pmatrix} F_{k-1} \\ F_{k-2} \end{pmatrix} ( F k  F k − 1   ) = ( E I  I − D 0  ) ⋅ ( F k − 1  F k − 2   ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include  <bits/stdc++.h>  using  namespace  std;struct  matrix  : vector<vector<int >> {	int  n, m; 	matrix (int  n) : n (n), m (n) {         assign (n, vector <int >(n, 0 ));     } 	matrix (int  n, int  m, int  k = 0 ) : n (n), m (m) {         assign (n, vector <int >(m));         for  (int  i = 0 ; i < min (n, m); i++) {             (*this )[i][i] = k;         }     } }; constexpr  int  mod = 998244353 ;matrix operator  * (const  matrix &x, const  matrix &y) { 	assert (x.m == y.n); 	matrix z (x.n, y.m)  ; 	for  (int  k = 0 ; k < x.m; k++) { 		for  (int  i = 0 ; i < x.n; i++) { 			for  (int  j = 0 ; j < y.m; j++) { 				z[i][j] += 1LL  * x[i][k] * y[k][j] % mod; 				z[i][j] %= mod; 			} 		} 	} 	return  z; } matrix qpow (matrix a, int  k)   {	assert (a.n == a.m); 	matrix res (a.n, a.n, 1 )  ; 	for  (; k; k >>= 1 , a = a * a) { 		if  (k & 1 ) res = res * a; 	} 	return  res; } signed  main ()  	ios::sync_with_stdio (false ); 	cin.tie (nullptr );     int  n, m, k;     cin >> n >> m >> k;     matrix E (n) , D (n) , I (n, n, 1 )  ;     for  (int  i = 0 ; i < m; i++) {         int  x, y;         cin >> x >> y;         x--;         y--;         E[x][y] = 1 ;         E[y][x] = 1 ;         D[x][x]++;         D[y][y]++;     }     matrix X1 = E;     matrix X2 = E * E;     matrix S (2  * n, n)  ;     for  (int  i = 0 ; i < n; i++) {         for  (int  j = 0 ; j < n; j++) {             S[i][j] = (X2[i][j] - D[i][j] + mod) % mod;             S[n + i][j] = X1[i][j];         }     }     matrix P (2  * n, 2  * n)  ;     for  (int  i = 0 ; i < n; i++) {         for  (int  j = 0 ; j < n; j++) {             P[i][j] = E[i][j];             P[n + i][j] = I[i][j];             P[i][n + j] = (I[i][j] - D[i][j] + mod) % mod;         }     }     cout << (qpow (P, k - 2 ) * S)[0 ][n - 1 ] << endl; 	return  0 ; } 
题意简洁,代码极短,需要一定思维的好题。
给定两个字符串S , T S,T S , T n , m n,m n , m S n ′ S'_{n} S n ′  
第一次操作:S 1 ′ = S 1 S_{1}' = S_{1} S 1 ′  = S 1   第二次至第n n n S i ′ = S i − 1 ′ + S i + S i − 1 ′ S_{i}' = S_{i-1}' + S_{i} +S_{i-1}' S i ′  = S i − 1 ′  + S i  + S i − 1 ′   求字符串T T T S n ′ S'_{n} S n ′  1 ⩽ ∣ S ∣ ⩽ 100 1 \leqslant |S| \leqslant 100 1 ⩽ ∣ S ∣ ⩽ 1 0 0 1 ⩽ ∣ T ∣ ⩽ 100 1 \leqslant |T| \leqslant 100 1 ⩽ ∣ T ∣ ⩽ 1 0 0 
对于一个字符串A A A ( ∣ T ∣ + 1 ) × ( ∣ T ∣ + 1 ) (|T| + 1) \times (|T| + 1) ( ∣ T ∣ + 1 ) × ( ∣ T ∣ + 1 ) mat  ( A ) \operatorname{mat}(A) m a t ( A ) mat  ( A ) l , r \operatorname{mat}(A)_{l,r} m a t ( A ) l , r  T T T [ l , r ) [l,r) [ l , r ) A A A mat  ( A ) i i = 1 \operatorname{mat}(A)_{ii} = 1 m a t ( A ) i i  = 1 
于是,字符串拼接S i ′ = S i − 1 ′ + S i + S i − 1 ′ S_{i}' = S_{i-1}' + S_{i} +S_{i-1}' S i ′  = S i − 1 ′  + S i  + S i − 1 ′  
mat  ( S i ′ ) = mat  ( S i − 1 ′ ) × mat  ( S i ) × mat  ( S i − 1 ′ ) \operatorname{mat}(S_{i}') = \operatorname{mat}(S_{i-1}') \times \operatorname{mat}(S_{i}) \times \operatorname{mat}(S_{i-1}') m a t ( S i ′  ) = m a t ( S i − 1 ′  ) × m a t ( S i  ) × m a t ( S i − 1 ′  ) 
单个字符对应的矩阵mat  ( S i ) \operatorname{mat}(S_{i}) m a t ( S i  ) O ( ∣ S ∣ ∣ T ∣ 3 ) \mathcal{O}(|S| |T|^{3}) O ( ∣ S ∣ ∣ T ∣ 3 ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include  <bits/stdc++.h>  using  namespace  std;constexpr  int  P = 998'244'353 ;struct  Matrix  : vector<vector<int >> {    Matrix (int  n, int  num) {         resize (n, vector <int >(n));         for  (int  i = 0 ; i < n; i++) {             (*this )[i][i] = num;         }     } }; constexpr  Matrix operator  * (const  Matrix& A, const  Matrix& B) {    int  n = A.size ();     Matrix C (n, 0 )  ;     for  (int  k = 0 ; k < n; k++) {         for  (int  i = 0 ; i < n; i++) {             for  (int  j = 0 ; j < n; j++) {                 C[i][j] = (C[i][j] + 1LL  * A[i][k] * B[k][j]) % P;             }	         }	     }	     return  C; } int  main ()      string s, t;     cin >> s >> t; 	Matrix res (t.size() + 1 , 1 )  ; 	for  (int  i = 0 ; i < s.size (); i++) {         Matrix M (t.size() + 1 , 1 )  ;         for  (int  j = 0 ; j < t.size (); j++) {             M[j][j + 1 ] = s[i] == t[j];         } 		res = res * M * res; 	} 	cout << res[0 ][t.size ()] << endl;       return  0 ; } 
给定一长度为N N N V = 52 V = 52 V = 5 2 S S S K K K K K K S S S N ⩽ 5 × 1 0 5 , K ⩽ 1 0 9 N \leqslant 5\times 10^{5}, K \leqslant 10^{9} N ⩽ 5 × 1 0 5 , K ⩽ 1 0 9 
希望每个本质不同的串只被拼接组合一次。
设f i f_{i} f i  i i i f i f_{i} f i  f j f_{j} f j  M i j M_{ij} M i j  i i i j j j S S S 
例如,S = ( a , b ) S = (a,b) S = ( a , b ) 
M = [ 2 1 2 1 1 1 0 0 1 ] M 2 = [ 5 3 7 3 2 4 0 0 1 ] \boldsymbol{M} = \begin{bmatrix} 2 & 1 & 2 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \\ \end{bmatrix} \quad \quad \boldsymbol{M}^{2} = \begin{bmatrix} 5 & 3 & 7 \\ 3 & 2 & 4 \\ 0 & 0 & 1 \\ \end{bmatrix} M = ⎣ ⎢ ⎡  2 1 0  1 1 0  2 1 1  ⎦ ⎥ ⎤  M 2 = ⎣ ⎢ ⎡  5 3 0  3 2 0  7 4 1  ⎦ ⎥ ⎤  
于是K = 1 K = 1 K = 1 2 + 1 + 1 = 4 2+1+1=4 2 + 1 + 1 = 4 K = 2 K = 2 K = 2 7 + 4 + 1 = 12 7+4+1=12 7 + 4 + 1 = 1 2 
用后缀自动机预处理转移系数,用矩阵快速幂优化转移,复杂度O ( N V + V 3 log  K ) \mathcal{O}(NV + V^{3} \log K) O ( N V + V 3 log  K ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 constexpr  int  N = ALPHABET + 1 ;using  Matrix = array<array<Z, N>, N>;Matrix operator  * (const  Matrix& A, const  Matrix& B) {     Matrix C{};     for  (int  i = 0 ; i < N; i++) {         for  (int  j = 0 ; j < N; j++) {             for  (int  k = 0 ; k < N; k++) {                 C[i][j] += A[i][k] * B[k][j];             }         }     }     return  C; } void  solve ()      int  N, K;     cin >> N >> K;     string s;     cin >> s;     Node* root = newNode ();     Node* p = root;     for  (auto & c : s) {         if  (isupper (c)) {             c = c - 'A'  + 26 ;          } else  {             c = c - 'a' ;          }         p = extend (root, p, c);     } 	sort ();          Matrix coef{};     coef[ALPHABET][ALPHABET] = 1 ;      for  (auto  p : order) {         for  (auto  b = 0 ; b < ALPHABET; b++) {             if  (p->next[b]) {                 p->nexts.push_back (b);             }          }     }          for  (auto  i = 0 ; i < ALPHABET; i++) {         if  (!root->next[i]) {             continue ;          }                  auto  s = root->next[i];         for  (auto  p : order) {             p->dp = 0 ;          }         s->dp = 1 ; 		 		         for  (auto  p : sam.order) {             for  (auto  j = 0 ; j < ALPHABET; j++) {                 if  (p->next[j]) {                     p->next[j]->dp += p->dp;                 } else  {                     coef[i][j] += p->dp;                 }             }             coef[i][ALPHABET] += p->dp;         }          		         Z sum = 0 ;         for  (auto  p : order) {             for  (auto  b : p->nexts) {                 p->next[b]->dp += p->dp;             }             sum += p->dp;         }         for  (auto  b = 0 ; b <= ALPHABET; b++) {             coef[i][b] = sum;         }                 for  (auto  p : order) {             for  (auto  b : p->nexts) {                 coef[i][b] -= p->dp;             }         }     }          Matrix res = qpow (coef, K);          Z ans = 0 ;     for  (auto  i = 0 ; i <= ALPHABET; i++) {         ans += res[i][ALPHABET];      }     cout << ans << endl; } 
把序列切三段,最大化每段都出现的元素数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 struct  Lazy  {    int  add{};     void  apply (const  Lazy& o)  &          add += o.add;     } }; struct  Info  {    int  maxx{ -inf };     int  imax{};     void  apply (const  Lazy& o)  &          maxx += o.add;     } }; Info operator  + (const  Info& l, const  Info& r) {     if  (l.maxx > r.maxx) {         return  l;     } else  {         return  r;     } } void  eachT ()      int  n;     cin >> n;     vector<int > a (n)  ;     for  (auto & x : a) {         cin >> x;     }     auto  alls = a;     sort (alls.begin (), alls.end ());     alls.erase (unique (alls.begin (), alls.end ()), alls.end ());     const  int  V = alls.size ();     vector<vector<int >> adj (V);     for  (int  i = 0 ; i < n; i++) {         a[i] = lower_bound (alls.begin (), alls.end (), a[i]) - alls.begin ();         adj[a[i]].push_back (i);     }     vector<Info> info (n)  ;     for  (int  i = 0 ; i < n; i++) {         info[i] = { 0 , i };     }     LazySegmentTree<Info, Lazy> seg (info)  ;     vector<pair<int , int >> cur (V);     int  resx = -1 , resl = -1 , resr = -1 ;     for  (int  i = 0 ; i < n; i++) {         int  x = a[i];         auto  it = upper_bound (adj[x].begin (), adj[x].end (), i);         int  l = it == adj[x].end () ? n : *it;         int  r = adj[x].back ();         seg.update (cur[x].first, cur[x].second, { -1  });         cur[x] = { l + 1 , r + 1  };         seg.update (cur[x].first, cur[x].second, { 1  });         auto  res = seg.query (i, n);         if  (res.maxx > resx) {             resx = res.maxx;             resl = i + 1 ;             resr = res.imax;         }     }     cout << resx << endl;     cout << resl + 1  << " "  << resr + 1  << endl; } 
求序列的划分方法数,满足每段中每个数出现的次数都不在集合S S S 1 ⩽ a i ⩽ n ⩽ 2 × 1 0 5 ,   ∣ S ∣ ⩽ 10 1 \leqslant a_{i} \leqslant n \leqslant 2\times 10^{5},\ |S| \leqslant 10 1 ⩽ a i  ⩽ n ⩽ 2 × 1 0 5 ,   ∣ S ∣ ⩽ 1 0 
设f i f_{i} f i  i i i f i = ∑ 1 ⩽ j < i f j f_{i}=\sum \limits_{1 \leqslant j<i} f_{j} f i  = 1 ⩽ j < i ∑  f j  f i f_{i} f i  f j f_{j} f j  j j j f j f_{j} f j  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 constexpr  int  N = 2e5  + 5 , mod = 998244353 ;struct  mark  {	int  add{ 0  };    	void  apply (const  mark& o, int  s) &  		add += o.add; 	} }; struct  info  {	int  mn{ 0  };   	int  tot{ 0  };  	void  apply (const  mark& o, int  s) &  		mn += o.add; 	} 	void  apply (const  info& o) &  		*this  = o; 	} 	friend  info operator  + (const  info& l, const  info& r) { 		if  (l.mn < r.mn) return  l; 		if  (l.mn > r.mn) return  r; 		return  { l.mn, (l.tot + r.tot) % mod }; 	} }; void  eachT ()  	int  n, m; 	cin >> n >> m; 	vector<int > a (n)  ; 	for  (int  i = 0 ; i < n; ++i) { 		cin >> a[i];   	} 	vector<vector<int >> pos (n + 1 ); 	for  (int  v = 1 ; v <= n; ++v) { 		pos[v].push_back (0 ); 	} 	for  (int  i = 0 ; i < n; ++i) { 		pos[a[i]].push_back (i + 1 ); 	} 	for  (int  v = 1 ; v <= n; ++v) { 		pos[v].push_back (n + 1 ); 	} 	vector<vector<array<int , 3>>> c (n + 2 ); 	while  (m--) { 		int  x; 		cin >> x; 		for  (int  v = 1 ; v <= n; ++v) { 			for  (int  i = x; i + 1  < pos[v].size (); ++i) { 				int  L = pos[v][i - x]; 				int  R = pos[v][i - x + 1 ] - 1 ; 				int  st = pos[v][i]; 				int  ed = pos[v][i + 1 ]; 				c[st].push_back ({ L, R, 1  });   				c[ed].push_back ({ L, R, -1  });  			} 		} 	} 	vector<int > f (n + 2 )  ; 	SMT<info, mark> smt; 	smt.init (0 , n); 	smt.modify (0 , { 0 , 1  }); 	for  (int  i = 1 ; i <= n; ++i) { 		for  (auto & [l, r, val] : c[i]) { 			smt.modify (l, r, { val });            		} 		auto  qq = smt.query (0 , n); 		f[i] = qq.tot * (qq.mn == 0 );       		smt.modify (i, { 0 , f[i] });         	} 	cout << f[n] << '\n' ; } 
给定一个由 n n n a a a k k k 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 struct  mark  {	int  add{ 0  };    	void  apply (const  mark& o, int  s) &  		add += o.add; 	} }; struct  info  {	int  mn{ 0  };   	ll tot{ 0  };  	void  apply (const  mark& o, int  s) &  		mn += o.add; 	} 	void  apply (const  info& o) &  		*this  = o; 	} 	friend  info operator  + (const  info& l, const  info& r) { 		if  (l.mn < r.mn) return  l; 		if  (l.mn > r.mn) return  r; 		return  { l.mn, l.tot + r.tot }; 	} }; SMT<info, mark> smt; void  eachT ()  	int  n, k; 	cin >> n >> k; 	vector<int > a (n) , alls ; 	for  (int  i = 0 ; i < n; ++i) { 		cin >> a[i]; 		alls.push_back (a[i]); 	} 	sort (alls.begin (), alls.end ()); 	alls.erase (unique (alls.begin (), alls.end ()), alls.end ()); 	for  (int  i = 0 ; i < n; ++i) { 		a[i] = lower_bound (alls.begin (), alls.end (), a[i]) - alls.begin () + 1 ; 	} 	vector<vector<int >> pos (n + 1 ); 	for  (int  i = 0 ; i < n; ++i) { 		pos[a[i]].push_back (i + 1 ); 	} 	for  (int  v = 1 ; v <= n; ++v) { 		pos[v].push_back (n + 1 ); 	} 	vector<vector<array<int , 3>>> c (n + 2 ); 	for  (int  v = 1 ; v <= n; ++v) { 		for  (int  i = 0 ; i + 1  < pos[v].size (); ++i) { 			int  st = pos[v][i], ed = pos[v][i + 1 ]; 			 			if  (i >= k - 1 ) { 				int  L = i >= k ? pos[v][i - k] + 1  : 1 , R = pos[v][i - k + 1 ]; 				c[st].push_back ({ L, R, -1  }); 				c[ed].push_back ({ L, R, 1  }); 			} 			c[st].push_back ({ 1 , st, 1  }); 			c[ed].push_back ({ 1 , st, -1  }); 		} 	} 	smt.init (1 , n); 	for  (int  i = 1 ; i <= n; ++i) { 		smt.modify (i, { 0 , 1  }); 	} 	ll res = 0 ; 	for  (int  i = 1 ; i <= n; ++i) { 		for  (auto & [l, r, val] : c[i]) { 			smt.modify (l, r, { val }); 		} 		auto  qq = smt.query (1 , i); 		res += qq.tot * (qq.mn == 0 ); 	} 	cout << res << "\n" ; } 
[[…/《区域赛难题攻坚档案》/2024 杭电多校 /2024-07-29:2024“钉耙编程”中国大学生算法设计超级联赛(4)#4012 - 寻找宝藏 |2024-07-29:2024“钉耙编程”中国大学生算法设计超级联赛(4)]]
你得到了一张藏宝图,这上面标记了埋藏在地底下的n n n 1 1 1 n n n i i i ( i , p i ) (i,p_i) ( i , p i  ) v i v_i v i  
你现在位于( 0 , 0 ) (0,0) ( 0 , 0 ) ( x , y ) (x,y) ( x , y ) ( x + 1 , y ) (x+1,y) ( x + 1 , y ) ( x , y + 1 ) (x,y+1) ( x , y + 1 ) 
不幸的是,有一个危险的陷阱区域没有被标记出来!通过多方调研,你得知这是一个边平行坐标轴的矩形区域,它是m m m 
假设陷阱区域的位置分布是第i i i ( x 1 , y 1 ) (x_1,y_1) ( x 1  , y 1  ) ( x 2 , y 2 ) (x_2,y_2) ( x 2  , y 2  ) ( x , y ) (x,y) ( x , y ) x 1 ≤ x ≤ x 2 x_1\leq x\leq x_2 x 1  ≤ x ≤ x 2  y 1 ≤ y ≤ y 2 y_1\leq y\leq y_2 y 1  ≤ y ≤ y 2  i i i m − 1 m-1 m − 1 
2024 暑期集训补题(线段树优化 DP + 扫描线)-CSDN 博客 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 struct  info  {	ll mx{ 0  };   	void  apply (const  info& o) &  		*this  = o; 	} 	friend  info operator  + (const  info& l, const  info& r) { 		return  { max (l.mx, r.mx) }; 	} }; struct  Rectangle  {	int  L, R, D, U;  	ll LD;  	ll RU;  	ll ans;   } que[N]; vector<int > L[N], R[N]; int  p[N];ll f[N], g[N], h[N], w[N]; SMT<info> smt; void  eachT ()  	ll n, m; 	cin >> n >> m; 	for  (int  i = 1 ; i <= n; ++i) { 		cin >> p[i] >> w[i]; 		L[i].clear (); 		R[i].clear (); 	} 	for  (int  i = 1 ; i <= m; ++i) { 		cin >> que[i].L >> que[i].D >> que[i].R >> que[i].U; 		L[que[i].L].push_back (i); 		R[que[i].R].push_back (i); 	} 	smt.init (1 , n); 	for  (int  i = 1 ; i <= n; ++i) { 		for  (auto  id : L[i]) { 			que[id].LD = smt.query (1 , que[id].U).mx; 		} 		f[i] = smt.query (1 , p[i]).mx + w[i]; 		smt.modify (p[i], { f[i] }); 		for  (auto  id : R[i]) { 			que[id].RU = smt.query (1 , que[id].D - 1 ).mx; 		} 	} 	smt.init (1 , n); 	for  (int  i = n; i >= 1 ; i--) { 		for  (auto  id : R[i]) { 			que[id].RU += smt.query (que[id].D, n).mx; 		} 		g[i] = smt.query (p[i], n).mx + w[i]; 		h[i] = f[i] + g[i] - w[i]; 		smt.modify (p[i], { g[i] }); 		for  (auto  id : L[i]) { 			que[id].LD += smt.query (que[id].U + 1 , n).mx; 		} 	} 	for  (int  i = 1 ; i <= m; ++i) { 		que[i].ans = max (que[i].LD, que[i].RU); 	} 	smt.init (1 , n); 	for  (int  i = 1 ; i <= n; ++i) { 		for  (auto  id : L[i]) { 			chmax (que[id].ans, smt.query (que[id].U, n).mx); 		} 		smt.modify (p[i], { h[i] }); 	} 	smt.init (1 , n); 	for  (int  i = n; i >= 1 ; i--) { 		for  (auto  id : R[i]) { 			que[id].ans = max (que[id].ans, smt.query (1 , que[id].D).mx); 		} 		smt.modify (p[i], { h[i] }); 	} 	for  (int  i = 1 ; i <= m; ++i) { 		cout << que[i].ans << '\n' ; 	} }