【学习笔记】浅谈广义矩阵乘法——动态DP
文章目錄
- 廣義矩陣乘法
- 動態DP
- 例題:洛谷4719
以下內容是本人做題經驗,如有雷同,純屬抄襲;如有不對,純屬不懂,還請指正
廣義矩陣乘法
眾所周知,矩陣滿足乘法交換律,前一個矩陣的列必須是后一個矩陣的行
n×pn\times pn×p的AAA矩陣和p×mp\times mp×m的BBB矩陣相乘得到CCC矩陣
Ci,j=∑k=1pAi,k×Bk,jC_{i,j}=\sum_{k=1}^pA_{i,k}\times B_{k,j}Ci,j?=k=1∑p?Ai,k?×Bk,j?
假設矩陣D=A?B?CD=A*B*CD=A?B?C,考慮DDD是如何生成的
Di,j=∑k=1n((∑p=1nAi,p×Bp,k)×Ck,j)D_{i,j}=\sum_{k=1}^n\left ( (\sum_{p=1}^nA_{i,p}\times B_{p,k}) \times C_{k,j} \right)Di,j?=k=1∑n?((p=1∑n?Ai,p?×Bp,k?)×Ck,j?)=∑k=1n(∑p=1n(Ai,p×Bp,k×Ck,j))=\sum_{k=1}^n\left ( \sum_{p=1}^n(A_{i,p}\times B_{p,k} \times C_{k,j})\right)=k=1∑n?(p=1∑n?(Ai,p?×Bp,k?×Ck,j?))=∑k=1n(Ai,p×∑p=1n(Bp,k×Ck,j))=\sum_{k=1}^n\left ( A_{i,p}\times \sum_{p=1}^n(B_{p,k} \times C_{k,j})\right)=k=1∑n?(Ai,p?×p=1∑n?(Bp,k?×Ck,j?))=A?(B?C)=A*(B*C)=A?(B?C)
性質
如果把乘法和加法換成其他運算,opt1,opt2opt1,opt2opt1,opt2
只要滿足opt1對opt2滿足分配律,則其也滿足矩陣的結合律
就好比乘法對加法滿足分配律
因為a?(b+c)a*(b+c)a?(b+c),與aaa先乘進括號再相加的答案a?b+a?ca*b+a*ca?b+a?c一樣
opt1:仁兄,穩住
——opt2:你要搞爪子,我憋不住了
opt1:不要占著茅坑,先讓我來
然后opt1解放了,離開了,只剩下opt2繼續解決
再舉個減法和取最大值的例子
我們知道max(b?a,c?a)=max(b,c)?amax(b-a,c-a)=max(b,c)-amax(b?a,c?a)=max(b,c)?a
所以我們也可以說減法對取最大值滿足分配律
有了這個進階版本,我們就可以將一般平常寫的矩陣中的乘法和加法操作換掉
動態DP
很多題目并不是靜態的,往往伴隨著修改原內容的操作,導致DP值不再正確
暴力的想法,則是每次修改,就重新做一遍DP
顯然這樣除了TLE別無出路
然而這個時候——動態DP孕育而生
動態DP,如其名,生來就是解決這種改變影響之前的DP值的問題
一半來說優化算法都是往logloglog級下壓
動態DP就是借助廣義矩陣來進行快速優化的
這類題目,原始的轉移一定可以用矩陣乘法來加速做
然后修改,就變成了加速矩陣的修改
一般還會在線段樹上進行矩陣的合并
例題:洛谷4719
Step1:考慮沒有修改
就是一道非常板的樹形DP
設f[i][0/1]f[i][0/1]f[i][0/1]表示是否選iii時,子樹iii的最大值,111選,000不選
{f[i][0]=∑j∈sonimax(f[j][1],f[j][0])f[i][1]=∑j∈sonif[j][0]\left\{ \begin{aligned} f[i][0]=\sum_{j∈son_i}max(f[j][1],f[j][0])\\ f[i][1]=\sum_{j∈son_i}f[j][0]&&&&&&&&&&&&&&&&&&&&&&&&&\\ \end{aligned} \right. ??????????f[i][0]=j∈soni?∑?max(f[j][1],f[j][0])f[i][1]=j∈soni?∑?f[j][0]??????????????????????????
Step2:考慮特殊的鏈形式,假設仍然沒有修改
也就是說iii只有一個兒子jjj,就沒有之前所謂的求和罷了
{f[i][0]=max(f[j][1],f[j][0])f[i][1]=f[j][0]+w[i]\left\{ \begin{aligned} f[i][0]=max(f[j][1],f[j][0])\\ f[i][1]=f[j][0]+w[i]&&&&&&&&&&&&&&&&&&&&&&&&&\\ \end{aligned} \right. {f[i][0]=max(f[j][1],f[j][0])f[i][1]=f[j][0]+w[i]??????????????????????????
這里就可以用到前面所說的加法對于求最大值有分配律
人為的給f[i][1]f[i][1]f[i][1]轉移添加一個最大值,f[i][1]=max(f[j][0])+w[i]f[i][1]=max(f[j][0])+w[i]f[i][1]=max(f[j][0])+w[i]
那么就可以構造矩陣進行矩陣加速了
矩陣相乘時,為了防止f[j][1]f[j][1]f[j][1]對f[i][1]f[i][1]f[i][1]造成貢獻
此題可以將其矩陣位置上設個極小值
[00w[i]?INF]×[f[j][0]f[j][1]]=[f[i][0]f[i][1]]\begin{bmatrix} 0&0\\ w[i]&-INF\\ \end{bmatrix}\times \begin{bmatrix} f[j][0]\\ f[j][1] \end{bmatrix}=\begin{bmatrix} f[i][0]\\ f[i][1]\\ \end{bmatrix}[0w[i]?0?INF?]×[f[j][0]f[j][1]?]=[f[i][0]f[i][1]?]
Step3:考慮一般形式,仍然沒有修改
可以將加速矩陣重新構造為
[f′[i][0]f′[i][0]f′[i][1]?INF]×[f[j][0]f[j][1]]=[f[i][0]f[i][1]]\begin{bmatrix} f'[i][0]&f'[i][0]\\ f'[i][1]&-INF\\ \end{bmatrix}\times \begin{bmatrix} f[j][0]\\ f[j][1] \end{bmatrix}=\begin{bmatrix} f[i][0]\\ f[i][1]\\ \end{bmatrix}[f′[i][0]f′[i][1]?f′[i][0]?INF?]×[f[j][0]f[j][1]?]=[f[i][0]f[i][1]?]
- f′[i][0]f'[i][0]f′[i][0]表示除了兒子jjj貢獻之外的f[i][0]f[i][0]f[i][0]
- f′[i][1]f'[i][1]f′[i][1]表示除了兒子jjj貢獻之外的f[i][1]f[i][1]f[i][1]
意思就是我們要將兒子分為兩種,一種是特殊轉移的,另一種就是剩下的
也就是說這個兒子灰常忒殊,加上我們已經會做鏈的形式了
自然而然想到樹鏈剖分中的重兒子
對于重兒子直接維護這個矩陣,一條重鏈的矩乘可以用線段樹壓為logloglog
輕兒子就暴力轉移即可
輕兒子暴力轉移一次到父親,然后父親就爬自己所在的那條重鏈
Step4:現在考慮修改
有了樹鏈剖分,修改也變得可操作了
假設修改了iii點,除了iii和其父親的fff會受到影響,其余是不變的
所以我們只需要修改子樹iii和iii的祖先的fff值即可
首先更新不考慮重兒子的f[i][1]f[i][1]f[i][1]的值,更新iii的矩陣(每個點都有一個加速矩陣)
然后對iii所在重鏈,進行單點修改操作,更新fff和矩陣
然后爬到新的重鏈,設到達的點為xxx,之前重鏈的頂端對新的重鏈而言是輕兒子
所以要更新f′[x][0]f'[x][0]f′[x][0]和f′[x][1]f'[x][1]f′[x][1]
以此類推,一路往上修改,時間復雜度O(log?n2)O(\log n^2)O(logn2)
#include <cstdio> #include <vector> #include <cstring> #include <iostream> using namespace std; #define inf 1e12 #define maxn 100005 #define int long long struct matrix {int c[2][2];matrix(){}matrix( int x1, int x2, int x3, int x4 ) {c[0][0] = x1, c[0][1] = x2, c[1][0] = x3, c[1][1] = x4;}void clear() {memset( c, 0, sizeof( c ) );}int *operator [] ( int x ) { return c[x]; }matrix operator * ( matrix v ) const {matrix ans;ans.clear();for( int i = 0;i < 2;i ++ )for( int j = 0;j < 2;j ++ )for( int k = 0;k < 2;k ++ )ans[i][j] = max( ans[i][j], c[i][k] + v[k][j] );return ans;} }tree[maxn << 2], tmp[maxn]; vector < int > G[maxn]; int n, m, cnt; int a[maxn]; int siz[maxn], f[maxn], son[maxn]; int dfn[maxn], id[maxn], top[maxn], bot[maxn]; int dp[maxn][2];void dfs1( int u, int fa ) {siz[u] = 1, f[u] = fa;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa ) continue;dfs1( v, u );siz[u] += siz[v];if( siz[v] > siz[son[u]] || ! son[u] )son[u] = v;} }void dfs2( int u, int t ) {top[u] = t, dfn[u] = ++ cnt, id[cnt] = u;if( son[u] ) dfs2( son[u], t ), bot[u] = bot[son[u]];else bot[u] = u;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == f[u] || v == son[u] ) continue;else dfs2( v, v );} }void dfs( int u ) {dp[u][0] = 0, dp[u][1] = a[u];for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == f[u] ) continue;dfs( v );dp[u][1] += dp[v][0];dp[u][0] += max( dp[v][1], dp[v][0] );} }void build( int num, int l, int r ) {if( l == r ) {int u = id[l], f1 = a[u], f0 = 0;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == f[u] || v == son[u] ) continue;f1 += dp[v][0], f0 += max( dp[v][0], dp[v][1] );}tree[num] = tmp[l] = matrix( f0, f0, f1, -inf );return;}int mid = ( l + r ) >> 1;build( num << 1, l, mid );build( num << 1 | 1, mid + 1, r );tree[num] = tree[num << 1] * tree[num << 1 | 1]; }void modify( int num, int l, int r, int pos ) {if( l == r ) { tree[num] = tmp[l]; return; }int mid = ( l + r ) >> 1;if( pos <= mid ) modify( num << 1, l, mid, pos );else modify( num << 1 | 1, mid + 1, r, pos );tree[num] = tree[num << 1] * tree[num << 1 | 1]; }matrix query( int num, int l, int r, int L, int R ) {if( L <= l && r <= R ) return tree[num];int mid = ( l + r ) >> 1;if( R <= mid ) return query( num << 1, l, mid, L, R );else if( mid < L ) return query( num << 1 | 1, mid + 1, r, L, R );else return query( num << 1, l, mid, L, R ) * query( num << 1 | 1, mid + 1, r, L, R ); }void modify( int u, int w ) {tmp[dfn[u]][1][0] += w - a[u], a[u] = w;while( u ) {matrix last = query( 1, 1, n, dfn[top[u]], dfn[bot[u]] );modify( 1, 1, n, dfn[u] );matrix now = query( 1, 1, n, dfn[top[u]], dfn[bot[u]] );u = f[top[u]];if( ! u ) break;int p = dfn[u];tmp[p][0][0] = tmp[p][0][1] = tmp[p][0][0] + max( now[0][0], now[1][0] ) - max( last[0][0], last[1][0] );tmp[p][1][0] = tmp[p][1][0] + now[0][0] - last[0][0];} }signed main() {scanf( "%lld %lld", &n, &m );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%lld %lld", &u, &v );G[u].push_back( v );G[v].push_back( u ); }dfs1( 1, 0 );dfs2( 1, 1 );dfs( 1 );build( 1, 1, n );for( int i = 1, x, y;i <= m;i ++ ) {scanf( "%lld %lld", &x, &y );modify( x, y ); matrix ans = query( 1, 1, n, 1, dfn[bot[1]] );printf( "%lld\n", max( ans[0][0], ans[1][0] ) );}return 0; } 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的【学习笔记】浅谈广义矩阵乘法——动态DP的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CF407 E. k-d-sequenc
- 下一篇: [2021-09-11 CQBZ/HSZ