1. 程式人生 > 其它 >ENSENSO 3D雙目相機垂直測量規則物體點雲體積

ENSENSO 3D雙目相機垂直測量規則物體點雲體積

動態 \(DP\)

引入:

一般來說,樹上 \(DP\) 問題是不做改變的,只用計算一遍就行了。

但是給你說:更改一個點的權值,再去詢問你答案。

這時候再算一遍?或者是隻處理這個點的父親部分?但是這樣萬一成鏈,那不是時間複雜度又爆了?

所以,我們引入了 動態 \(DP\) 這個概念。

解決問題:

直接說怎麼做不好說,還是用例子解決。

例題:

給定一棵 \(n\) 個點的樹。\(i\) 號點的點權為 \(a_i\)。有 \(m\) 次操作,每次操作給定 \(u,w\) 表示修改點 \(u\) 的權值為 \(w\)。你需要在每次操作之後求出這棵樹的最大權獨立集的權值大小。


考慮沒有修改做法

最大權獨立集可以理解成相連的點不能同時選,因此很容易轉移:設 \(dp[i][0/1]\)

表示 \(i\) 點為根選擇/不選擇 \(i\) 時的最大值,則有轉移方程:

\[dp[i][0]=\sum_j max(dp[j][0],dp[j][1]) \]\[dp[i][1]=\sum_j dp[j][0]+val[i] \]

其中若 \(i\) 為葉子節點,則 \(dp[i][0]=0,dp[i][1]=val[i]\)

答案就是 \(max(dp[1][0],dp[1][1])\).


帶上修改

這裡,我們使用了重鏈剖分。因為有以下性質:

  1. 不會形成特別長的鏈,時間複雜度不會炸
  2. 重鏈的鏈尾都是葉子節點,且只有葉子節點無重兒子
  3. 一條重鏈所在的區間在 \(dfs\) 序上是連續的一段區間,可以用資料結構維護。

我們考慮一些微觀問題:在一條鏈裡,怎麼支援快速修改和查詢這條鏈的 \(DP\)

我們定義一個 \(g\) 陣列:

\(g[i][1]\) 表示 \(i\) 點的輕兒子都不取的最大權獨立集

\(g[i][0]\) 表示 \(i\) 點的輕兒子可取可不取形成的最大權獨立集。

其中,對於葉子節點 \(g[i][0]=g[i][1]=0\) , \(j\)\(i\) 的重兒子,則有方程:

$$dp[i][0]=g[i][0]+max(dp[j][0],dp[j][1])$$

\[dp[i][1]=g[i][1]+a_i+dp[j][0] \]

第二個式子繼續優化: \(g[i][1]\) 表示只考慮輕兒子取自己的最大權獨立集,所以變成:

$$dp[i][1]=g[i][1]+dp[j][0]$$


進行區間維護

我們考慮像斐波那契一樣,維護這個方程的矩陣:

\[\quad \begin{vmatrix} dp[j][0] & dp[j][1] \end{vmatrix} \quad \]

現在我們要從一個點的重兒子 \(j\) 轉移到 \(i\) 上,也就是說我們需要構造出一個轉移矩陣使: \( \quad \begin{vmatrix} dp[j][0] & dp[j][1] \end{vmatrix} \quad \) 能轉移到 \( \quad \begin{vmatrix} dp[i][0] & dp[i][1] \end{vmatrix} \quad \)

好像並不能進行矩陣乘法。 但是,我們可以重新定義:

《他改變了矩乘》: 我們定義一個新的運算子 \(*\) ,對於矩陣 \(A,B\) ,定義 \(A*B\) 的結果為 \(C\) ,滿足:

\[C[i][j]=\max\limits_k (A[i][k]+B[k][j]) \]

實現到程式碼中,則有:

struct matrix{
    int mat[2][2];
    matrix(){memset(mat,-0X3F,sizeof(mat));}
    inline matrix operator *(matrix a,matrix b){
        matrix c;
        for(int i=0;i<2;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++)
            c.mat[i][j]=max(c.mat[i][j],a.mat[i][k]+b.mat[k][j]);
        return c;
    }
};

為什麼這個東西具有結合律?

口胡理解是: \(\max\)\(+\) 操作都是滿足結合律的,所以這個也滿足。


構造轉移矩陣

我們對轉移方程進行變形:

\(dp[i][0]=max(dp[j][0]+g[i][0],dp[j][1]+g[i][0])\)

\(dp[i][1]=max(dp[j][0]+g[i][1],-inf)\)

接著把已知的狀態和要轉移的狀態寫在一起,未知矩陣由 \(X\) 代替:

\[\quad \begin{vmatrix} dp[j][0] & dp[j][1] \end{vmatrix} \quad ∗ \quad \begin{vmatrix} X \end{vmatrix} \quad = \quad \begin{vmatrix} dp[i][0] & dp[i][1] \end{vmatrix} \quad \]

原來是個 \(1*2\) 的矩陣,形成 \(1*2\) 的矩陣,那麼 \(X\) 應該是一個 \(2*2\) 的矩陣。

我們設矩陣左上,右上,左下,右下四個位置分別為: \(x_1,x_2,x_3,x_4\) ,把每個位置對應上去:

\(dp[i][0]=\max(dp[j][0]+x_1,dp[j][1]+x_3)\) , 所以 \(x_1=g[i][0]\) , \(x_3=g[i][0]\)

\(dp[i][1]=\max(dp[j][0]+x_2,dp[j][1]+x_4)\) , 所以 \(x_2=g[i][1]\) , \(x_4=-inf\)

重鏈剖分剖出的 \(DFS\) 序,由於先訪問了鏈頭,所以這個區間中,鏈頭在區間左端,鏈尾在區間右端。

我們儲存的初始資訊在葉子節點(也就是鏈尾)上,因此我們的 “矩陣乘法” 應當是轉移矩陣 \((g)\) 在前,要維護的值矩陣 \((dp)\) 在後。

因此,則有:

\[\quad \begin{vmatrix} g[i][0] & g[i][0] \\ g[i][1] & -inf \end{vmatrix} \quad ∗ \quad \begin{vmatrix} dp[j][0] \\ dp[j][1] \end{vmatrix} \quad = \quad \begin{vmatrix} dp[i][0] \\ dp[i][1] \end{vmatrix} \quad \]

總體流程

我們對於一條重鏈,我們的葉子節點就儲存了最初始的值,鏈上每個節點都對應著一個轉移矩陣。

因為這個轉移矩陣滿足結合率,且和重鏈資訊沒關係,對於一條重鏈,我們可以之間線段樹維護區間乘積。

然後到了一條重鏈鏈頭,因為這個點是它父親的輕兒子,我們需要更新它父親節點所在的點的轉移矩陣。這樣子一直跳到根節點就可以了。

細節部分:

  1. 對於一個點,查詢其 \(dp\) 值,需要從這個點一直查到區間的鏈尾。
    因此,樹剖時我們多維護一個 \(end[i]\) ( \(i\) 是一條重鏈的鏈頭) ,表示\(i\) 為鏈頭的這條鏈,鏈尾(葉子節點) 在 \(DFS\) 序上的位置

  2. 更新線段樹上某個點的轉移矩陣時,傳入的如果是矩陣,遞迴下去常數太大。
    解決方法:線上段樹外,維護一個矩陣組 \(val[i]\) , 表示每個點對應的轉移矩陣,這樣更新時,直接賦值進來即可。

程式碼:

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5,M=2e5+5,V=4e5+5,INF=0x7F7F7F7F;

struct matrix{
    int mat[2][2];
    matrix(){memset(mat,-0X3F,sizeof(mat));}
    inline matrix operator * (matrix b){
        matrix c;
        for(int i=0;i<2;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++)
            c.mat[i][j]=max(c.mat[i][j],mat[i][k]+b.mat[k][j]);
        return c;
    }
};


int n,m;
int head[N],nxt[M],ver[M],tot;
int A[N];
int fa[N],sizes[N],dep[N],dfn[N],top[N],id[N],son[N],End[N],cnt;
int dp[N][2];
matrix val[N];

struct SegTree{
    int L[V],R[V]; matrix M[V];
    void push_up(int x){M[x]=M[x<<1]*M[x<<1|1];}//更新就更新對應矩陣
    void build(int l,int r,int x){
        L[x]=l; R[x]=r;
        if(l==r){
            M[x]=val[dfn[L[x]]]; return;//賦值方式
        }
        int mid=l+r>>1;
        build(l,mid,x<<1); build(mid+1,r,x<<1|1);
        push_up(x);
    }
    void update(int pos,int x){
        if(L[x]==R[x]){
            M[x]=val[dfn[pos]];
            return;
        }
        int mid=(L[x]+R[x])>>1;
        if(pos<=mid) update(pos,x<<1);
        else update(pos,x<<1|1);
        push_up(x);
    }
    matrix query(int l,int r,int x){
        if(L[x]==l&&R[x]==r) return M[x];
        int mid=(L[x]+R[x])>>1;
        if(r<=mid) return query(l,r,x<<1);
        else if(l>mid) return query(l,r,x<<1|1);
        else return query(l,mid,x<<1)*query(mid+1,r,x<<1|1);//dp即矩陣
    }
}T;

void add(int x,int y){
    ver[++tot]=y; nxt[tot]=head[x];head[x]=tot;
}

void dfs1(int x,int father){
    sizes[x]=1;
    dep[x]=dep[father]+1;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==father) continue;
        fa[y]=x; 
        dfs1(y,x);
        sizes[x]+=sizes[y];
        if(sizes[y]>sizes[son[x]]||!son[x]) son[x]=y;

    }
}
void dfs2(int x,int topfather){
    id[x]=++cnt; dfn[cnt]=x;
    top[x]=topfather;
    End[topfather]=max(End[topfather],cnt);//end陣列記錄鏈頭對應鏈尾位置
    //更新g陣列,即更新val

    dp[x][0]=0; dp[x][1]=A[x];
    val[x].mat[0][0]=val[x].mat[0][1]=0;//記錄每個點的矩陣
    val[x].mat[1][0]=A[x];

    if(son[x]!=0){
        dfs2(son[x],topfather);
        dp[x][0]+=max(dp[son[x]][0],dp[son[x]][1]);
        dp[x][1]+=dp[son[x]][0];
    }
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa[x]||y==son[x]) continue;
        dfs2(y,y);

        dp[x][0]+=max(dp[y][0],dp[y][1]);
        dp[x][1]+=dp[y][0];
        val[x].mat[0][0]+=max(dp[y][0],dp[y][1]);//子兒子可選可不選的最大獨立集
        val[x].mat[0][1]=val[x].mat[0][0];//同上
        val[x].mat[1][0]+=dp[y][0];//子兒子不能選
    }
}
void update_path(int x,int z){
    val[x].mat[1][0]+=z-A[x];//因為這個地方還存的有子兒子不能選的值,因此不能直接賦值為 z, 
    A[x]=z;//更新對應點的值
    matrix last,now;
    while(x!=0){//不斷進行更新
        last=T.query(id[top[x]],End[top[x]],1);//
        T.update(id[x],1);//找到需要更改的地方,進行更新
        now=T.query(id[top[x]],End[top[x]],1);
        x=fa[top[x]];//一直找重鏈,直到頂端

        val[x].mat[0][0]+=max(now.mat[0][0],now.mat[1][0])-max(last.mat[0][0],last.mat[1][0]);
        //之前可選可不選-現在可選可不選
        val[x].mat[0][1]=val[x].mat[0][0];
        val[x].mat[1][0]+=now.mat[0][0]-last.mat[0][0];
        //之前不選-現在不選
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%d",&A[i]);
    for(int i=1,x,y;i<n;i++){
        scanf("%d%d",&x,&y); add(x,y); add(y,x);
    } 
    dfs1(1,0);   
    dfs2(1,1); 
    T.build(1,n,1);
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y);
        update_path(x,y);
        matrix ans=T.query(id[1],End[1],1);
        printf("%d\n",max(ans.mat[0][0],ans.mat[1][0]));
    }
    system("pause");
    return 0;
}