动态DP 学习笔记

【学习笔记】动态规划—各种 DP 优化 设 j=p∗v[i]+r�=�∗�[�]+�,那么原方程可改为: dp[p∗v[i]+r]=max{dp[r+k∗v[i]]+(p−k)∗w[i]}��[�∗�[�]+�]=���{��[�+�∗�[�]]+(�−�)∗�[�]} (r∈[0,v[i]−1],(�∈[0,�[�]−1], p∈[1,⌊(V−r)/v[i]⌋],�∈[1,⌊(�−�)/�[�]⌋], k∈[p−min(⌊V/w[i]⌋,c[i]),p])�∈[�−���(⌊�/�[�]⌋,�[�]),�]) 阅读详情

前言

温馨提示:本文将会讲述树链剖分(会提到 LCT 的做法)和全局平衡二叉树两种方法,所以强烈建议两种方法都想学习的读者仔细阅读完本文!(真不要脸)


动态 DP,它的模板题之所以和动态树分治放在一起,是因为它们有两个共同特点:

  1. 它们在没有修改时都是用于维护树上信息的
  2. 它们都是带修改的,即动态的

所以,动态 DP 一般是用于解决动态维护树上信息的问题

这类问题最初只是可爱的树形 DP,却被丧心病狂的出题人套上了修改操作,褪去了原本善良的模样


那么,在学习动态 DP 之前,你需要掌握以下前置知识:

  1. DP
  2. 矩阵乘法(以及其对 DP 的优化)
  3. 树链剖分(包括链上的线段树操作)
  4. 二叉查找树(亦称二叉排序树)

阅读完以上内容,我们就开始吧!

正文

放上模板题(简单版):P4719 【模板】“动态 DP”&动态树分治

我们先来考虑这样一件事情:如果没有修改,这道题你应该怎么做?

(大概是人人都会吧,要不然你也不会点进来看到这句话)

我们可以设 f u , 0 / 1 f_{u,0/1} fu,0/1​ 表示如果点 u u u 不选/选,该子树的答案是多少

那么显然有转移方程:(点 v v v 为点 u u u 的子节点, v a l u val_u valu​ 为点 u u u 的点权)
f u , 0 = ∑ max ⁡ ( f v , 0 , f v , 1 ) f u , 1 = v a l u + ∑ f v , 0 \begin{aligned} & f_{u, 0} = \sum \max(f_{v, 0}, f_{v, 1}) \\ & f_{u, 1} = val_u + \sum f_{v, 0} \end{aligned} ​fu,0​=∑max(fv,0​,fv,1​)fu,1​=valu​+∑fv,0​​
临界情况显然:对于叶子节点 l l l,有 f l , 0 = 0 , f l , 1 = v a l l f_{l, 0} = 0, f_{l, 1} = val_l fl,0​=0,fl,1​=vall​

好,现在我们已经会 m = 0 m = 0 m=0 的部分分了!(然而这题并没有给,太不照顾作者这样的蒟蒻了)


接下来我们考虑带修

可以发现单点的修改操作最多只会修改一条链上的 f f f 值,所以我们可以想到用树链剖分维护树上的信息

为什么用树链剖分?

  1. 众所周知,一个点到根节点的路径上,至多只会经过 log ⁡ n \log_n logn​ 数量级的重链,这就为时间复杂度提供了保证
  2. 众所周知,重链的底端都是从叶子节点,那么做 DP 时我们不需要花额外的力气去为其设置初始状态,同时也保证了树形结构自底向上转移的优美性
  3. 众所周知,树链剖分的优美性在于破树为序,所有的操作在维护的 DFS 序上进行,因此我们可以很方便地用修改序列的方式修改树

That’s why I choose 树链剖分!


为了迎合轻重儿子的设定,我们再设一个 g u , 0 / 1 g_{u, 0/1} gu,0/1​,其中 g u , 0 g_{u, 0} gu,0​ 表示点 u u u 的所有轻儿子可选可不选~~(任其生死)~~的轻子树答案之和, g u , 1 g_{u, 1} gu,1​ 表示点 u u u 的所有轻儿子都不选的轻子树答案之和(下面的转移方程中为了保持结构优美性,把 v a l u val_u valu​ 加到了 g u , 1 g_{u, 1} gu,1​ 中)

所以状态转移方程就可以更新为:(此时点 v v v 为点 u u u 的重儿子)
f u , 0 = g u , 0 + max ⁡ ( f v , 0 , f v , 1 ) f u , 1 = g u , 1 + f v , 0 \begin{aligned} f_{u, 0} &= g_{u, 0} + \max(f_{v, 0}, f_{v, 1}) \\ f_{u, 1} &= g_{u, 1} + f_{v, 0} \end{aligned} fu,0​fu,1​​=gu,0​+max(fv,0​,fv,1​)=gu,1​+fv,0​​
(g 临界情况同 f)

结构是不是优美许多了?至少不带求和符号了

那么,如果我们能够把 DP 结构化,使得我们每次修改只是修改一个地方的转移状态而不关心它的 DP 值具体修改成了什么,那么我们就能够用树剖的链上修改和查询(实质是线段树的单点修改和区间查询)来完成这些操作


说到 DP 结构化,就不得不提到一个东西——矩阵乘法

既然第二维有 0 / 1 0/1 0/1 两种状态,那我们设出这个状态矩阵:
[ f u , 0 f u , 1 ] \left[\begin{matrix} f_{u, 0} \\ f_{u, 1} \end{matrix}\right] [fu,0​fu,1​​]
然而你会发现,这个方程并不满足矩阵乘法的定义,因为包含有 max ⁡ \max max 运算

没有关系,我们可以尝试定义一种矩阵乘法:对于矩乘式 M ∗ N = R M * N = R M∗N=R,我们设:
R i , j = max ⁡ k ( M i , k + N k , j ) R_{i, j} = \max_k (M_{i, k} + N_{k, j}) Ri,j​=kmax​(Mi,k​+Nk,j​)
可以证明这个矩乘是满足结合律的,读者不妨用规模很小的矩阵手算一下 ( R ∗ S ) ∗ T (R * S) * T (R∗S)∗T 和 R ∗ ( S ∗ T ) R * (S * T) R∗(S∗T) 的结果(带参数),看它们是否相同


那么,我们就可以用线段树维护这个矩乘了:(但是要注意顺序,链顶在线段树左侧,线段树从左往右的顺序也是重链自上而下延伸的顺序)

T × [ f v , 0 f v , 1 ] = [ f u , 0 f u , 1 ] T \times \left[\begin{matrix} f_{v, 0} \\ f_{v, 1} \end{matrix}\right] =\left[\begin{matrix} f_{u, 0} \\ f_{u, 1} \end{matrix}\right] T×[fv,0​fv,1​​]=[fu,0​fu,1​​]

现在我们就来推左侧的转移矩阵 T T T,可以发现这是一个 2 ∗ 2 2 * 2 2∗2 的矩阵

我们把矩阵内的元素直接写成代数运算的形式有:

T × [ f v , 0 f v , 1 ] = [ max ⁡ ( T 1 , 1 + f v , 0 , T 1 , 2 + f v , 1 ) max ⁡ ( T 2 , 1 + f v , 0 , T 2 , 2 + f v , 1 ) ] T \times \left[\begin{matrix} f_{v, 0} \\ f_{v, 1} \end{matrix}\right] =\left[ \begin{matrix} \max(T_{1, 1} + f_{v, 0}, T_{1, 2} + f_{v, 1}) \\ \max(T_{2, 1} + f_{v, 0}, T_{2, 2} + f_{v, 1}) \end{matrix} \right] T×[fv,0​fv,1​​]=[max(T1,1​+fv,0​,T1,2​+fv,1​)max(T2,1​+fv,0​,T2,2​+fv,1​)​]

结合转移方程,那么 T T T 就显而易见了:

T = [ g u , 0 g u , 0 g u , 1 − ∞ ] T = \left[ \begin{matrix} g_{u, 0} & g_{u, 0} \\ g_{u, 1} & -\infin \end{matrix} \right] T=[gu,0​gu,1​​gu,0​−∞​]

可以发现叶子节点的转移矩阵的第一列刚好就是其状态矩阵,所以答案的状态矩阵就是根节点所在重链上所有转移矩阵的乘积,最后答案对 f r o o t , 0 f_{root, 0} froot,0​ 和 f r o o t , 1 f_{root, 1} froot,1​ 取一个 max ⁡ \max max 即可

这样就做完了


你没发现不对吗?g 是如何算出来的?

好吧其实是本蒟蒻偷懒了没放出来

(点 v v v 为点 u u u 的轻儿子)
g u , 0 = ∑ max ⁡ ( f v , 0 , f v , 1 ) g u , 1 = ∑ f v , 0 \begin{aligned} g_{u, 0} &= \sum \max(f_{v, 0}, f_{v, 1}) \\ g_{u, 1} &= \sum f_{v, 0} \end{aligned} gu,0​gu,1​​=∑max(fv,0​,fv,1​)=∑fv,0​​
然后你就会发现,把 g 代入转移方程,整个方程根本没变嘛!

其实,之所以把 g 的转移方程放在这里介绍~~,是为了避免你想多了~~,是因为我们可以直接用线段树求出某一个轻儿子 f 的修改值(因为每次最多只修改一个节点),再进一步求出 g,然后再更新转移矩阵,这样单次修改是带两个 log ⁡ \log log 的,在简单版里面可以跑过

总时间复杂度: O ( 2 3 n log ⁡ 2 2 n ) O(2^3 n \log^2_2 n) O(23nlog22​n)

贴上代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
#include<algorithm>

#define MAXN 100005
#define INF 0x3F3F3F3F

using namespace std;

int n, m, a[MAXN], tot, hd[MAXN], nxt[MAXN << 1], to[MAXN << 1];
int si[MAXN], hs[MAXN], fa[MAXN], top[MAXN], ed[MAXN], dfncnt, dfn[MAXN];
int f[MAXN][2];

struct Matrix
{
    int v[2][2];
    Matrix(int A = -INF, int B = -INF) { v[0][0] = v[0][1] = A, v[1][0] = B, v[1][1] = -INF; return; }
} mt[MAXN];

Matrix operator * (const Matrix &a, const Matrix &b)
{
    Matrix c;
    for(register int k = 0; k < 2; k++) for(register int i = 0; i < 2; i++) for(register int j = 0; j < 2; j++) c.v[i][j] = max(c.v[i][j], a.v[i][k] + b.v[k][j]);
    return c;
}

struct Segment_Tree
{
    #define ls (now << 1)
    #define rs (now << 1 | 1)
    #define mid ((l + r) >> 1)
    
    int nd[MAXN << 2];
    Matrix tr[MAXN << 2];

    inline void Pushup(int now) { return tr[now] = tr[ls] * tr[rs], void(); }

    inline void Build(int now, int l, int r)
    {
        if(l == r) return tr[now] = mt[nd[l]], void();
        return Build(ls, l, mid), Build(rs, mid + 1, r), Pushup(now);
    }

    inline void Modify(int now, int l, int r, int pos)
    {
        if(l == r) return tr[now] = mt[nd[l]], void();
        return (pos <= mid ? Modify(ls, l, mid, pos) : Modify(rs, mid + 1, r, pos)), Pushup(now);
    }

    inline Matrix Query(int now, int l, int r, int L, int R)
    {
        if(L <= l && r <= R) return tr[now];
        if(R <= mid) return Query(ls, l, mid, L, R);
        if(L > mid) return Query(rs, mid + 1, r, L, R);
        return Query(ls, l, mid, L, R) * Query(rs, mid + 1, r, L, R);
    }

    #undef ls
    #undef rs
    #undef mid
} st;

inline void Link(int u, int v) { return ++tot, nxt[tot] = hd[u], to[tot] = v, hd[u] = tot, void(); }

inline void DFS1(int u, int f)
{
    si[u] = 1, fa[u] = f;
    for(register int i = hd[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(v == f) continue;
        DFS1(v, u), si[u] += si[v];
        if(si[v] > si[hs[u]]) hs[u] = v;
    }
    return;
}

inline void DFS2(int u, int tp)
{
    dfn[u] = ++dfncnt, st.nd[dfncnt] = u, top[u] = tp, ed[tp] = max(ed[tp], dfncnt);
    f[u][0] = mt[u].v[0][0] = mt[u].v[0][1] = 0, f[u][1] = mt[u].v[1][0] = a[u];
    if(hs[u])
    {
        DFS2(hs[u], tp);
        f[u][0] = max(f[hs[u]][0], f[hs[u]][1]);
        f[u][1] += f[hs[u]][0];
    }
    for(register int i = hd[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(v == fa[u] || v == hs[u]) continue;
        DFS2(v, v);
        f[u][0] += max(f[v][0], f[v][1]);
        f[u][1] += f[v][0];
        mt[u].v[0][0] += max(f[v][0], f[v][1]);
        mt[u].v[0][1] = mt[u].v[0][0];
        mt[u].v[1][0] += f[v][0];
    }
    return;
}

inline void Modify(int u, int w)
{
    mt[u].v[1][0] += w - a[u], a[u] = w;
    Matrix pr, nt;
    while(true)
    {
        pr = st.Query(1, 1, n, dfn[top[u]], ed[top[u]]);
        st.Modify(1, 1, n, dfn[u]);
        nt = st.Query(1, 1, n, dfn[top[u]], ed[top[u]]);
        u = fa[top[u]];
        if(!u) break;
        mt[u].v[0][0] += max(nt.v[0][0], nt.v[1][0]) - max(pr.v[0][0], pr.v[1][0]);
        mt[u].v[0][1] = mt[u].v[0][0];
        mt[u].v[1][0] += nt.v[0][0] - pr.v[0][0];
    }
    return;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(register int i = 1; i <= n; i++) scanf("%d", a + i);
    for(register int i = 1; i < n; i++)
    {
        int u, v; scanf("%d%d", &u, &v);
        Link(u, v), Link(v, u);
    }
    DFS1(1, 0), DFS2(1, 1);
    st.Build(1, 1, n);
    while(m--)
    {
        int u, w; scanf("%d%d", &u, &w), Modify(u, w);
        Matrix ans = st.Query(1, 1, n, dfn[top[1]], ed[top[1]]);
        printf("%d\n", max(ans.v[0][0], ans.v[1][0]));
    }
    return 0;
}

前面说了,本文还会介绍 LCT 的做法~~(但我没有说给代码嗷)~~

其实和轻重链剖分差不太多,就是每个点上要多维护一个 f 数组(因为你无法用直接线段树求 f 数组的值)

为了方便计算,可以先把每个点都设为轻儿子,这样就可以很方便地初始化 g 数组了

Access 操作就类似树剖,不过每次 Splay 的时候要更新 f 数组,Splay 完就可以顺便更新 g 数组了

还有就是注意 Pushup 的时候矩乘的顺序为左子树-根节点-右子树

剩下的都是基本操作,没什么好说的

LCT 的时间复杂度是 O ( 2 3 n log ⁡ 2 n ) O(2^3 n \log_2 n) O(23nlog2​n),比轻重链剖分少了一个 log ⁡ \log log,但实现起来常数巨大


做完上面那题,相信你一定会抖擞精神打开这一题:P4751 【模板】“动态DP”&动态树分治(加强版)

稍微改改数组大小,然后自信地提交上去~~,带着绝望和迷茫的眼神,收获满屏的 TLE、MLE 和 RE~~

如果你还不气馁,码了一遍 LCT,结局无非是——在漫长的卡常之路上,终屈服于 LCT 的巨大常数

此时,你点开了题解,映入眼帘的七个大字,如一道温暖的光照入你的心间,化开了绝望与迷茫堆积而成的雪——

全局平衡二叉树


首先,全局平衡二叉树的做法是基于 LCT 做法的,只是换了一种常数较小的数据结构(因为本题并不需要动态修改树的结构)

全局平衡二叉树在重链剖分的基础上,把重边看做实边,轻边看做虚边,其实就类似于静态的 LCT

构造的话,我们可以先重链剖分,类似 LCT,在每条链上以深度为关键字维护一颗 BST,同时让它的树根指向链顶的父亲

那么我们如何让它全局平衡呢?答案是把链上的每个点赋一个点权,权值为 s i z e + 1 size + 1 size+1( s i z e size size 特指点 u u u 的所有轻子树大小之和),然后每次以点权为关键字将带权重心作为根节点,同时把整条链剖成两条并分治(这里的思想其实有些类似于点分治)

可以把这样的操作抽象于下面这张图:

即重链的每个点上都挂着若干颗轻子树,欲使得全局平衡,则需求出其带权重心(可以二分,当然也可以暴枚,不影响总复杂度)

这个时候会发现:修改如果暴力跳父亲,可以做到严格的 log ⁡ 2 n \log_2 n log2​n!证明也很简单,只需要一句话——以点 u u u 的父亲为根的子树大小至少是以它自己为根的子树大小的 2 倍,这样,从点 u u u 向上跳不超过 log ⁡ 2 n \log_2 n log2​n 次,子树大小就会达到 n n n,即跳到树根

这就是数据结构的奥妙——把暴力的方法通过与构造巧妙结合,达到一个趋于平衡的最优复杂度

所以,修改的时候我们可以直接暴力向上跳父亲(实际上就是 LCT 中的 Access 操作,只不过没有用 Splay 维护),类似 LCT 的做法,跳重边时(即在重链上跳时)维护 f 数组,跳轻边时维护 g 数组即可

贴上代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>

#define MAXN 1000005
#define INF 0x3F3F3F3F

using namespace std;

int n, m, a[MAXN], Lastans;
int tot, hd[MAXN], nxt[MAXN << 1], to[MAXN << 1];
int si[MAXN], fa[MAXN], hs[MAXN], len, seq[MAXN];

inline void Link(int u, int v) { return ++tot, nxt[tot] = hd[u], to[tot] = v, hd[u] = tot, void(); }

struct Matrix
{
    int v[2][2];
    Matrix() { v[0][0] = v[0][1] = v[1][0] = v[1][1] = -INF; return; }
};

Matrix operator * (const Matrix &a, const Matrix &b)
{
    Matrix c;
    for(register int k = 0; k < 2; k++) for(register int i = 0; i < 2; i++) for(register int j = 0; j < 2; j++) c.v[i][j] = max(c.v[i][j], a.v[i][k] + b.v[k][j]);
    return c;
}

struct Binary_Search_Tree
{
    int rt, ls[MAXN], rs[MAXN], fa[MAXN], f[MAXN][2];
    Matrix val[MAXN], sum[MAXN];

    inline void Pushup(int now) { return sum[now] = sum[ls[now]] * val[now] * sum[rs[now]], void(); }
    inline bool Checkroot(int u) { return ls[fa[u]] != u && rs[fa[u]] != u; }

    inline int Build(int l, int r)
    {
        int cnt = 0, pre = 0, pos, now;
        for(register int i = l; i <= r; i++) cnt += si[seq[i]] - si[hs[seq[i]]];
        for(register int i = l; i <= r; i++) if((pre += si[seq[i]] - si[hs[seq[i]]]) >= (cnt >> 1)) { pos = i, now = seq[i]; break; }
        if(l < pos) ls[now] = Build(l, pos - 1), fa[ls[now]] = now;
        if(pos < r) rs[now] = Build(pos + 1, r), fa[rs[now]] = now;
        return Pushup(now), now;
    }

    inline void Modify(int u, int w)
    {
        val[u].v[1][0] += w - a[u], a[u] = w;
        while(u)
        {
            if(Checkroot(u))
            {
                if(fa[u])
                {
                    val[fa[u]].v[0][0] -= max(sum[u].v[0][0], sum[u].v[1][0]);
                    val[fa[u]].v[1][0] -= sum[u].v[0][0];
                }
                Pushup(u);
                if(fa[u])
                {
                    val[fa[u]].v[0][0] += max(sum[u].v[0][0], sum[u].v[1][0]);
                    val[fa[u]].v[0][1] = val[fa[u]].v[0][0];
                    val[fa[u]].v[1][0] += sum[u].v[0][0];
                }
            }
            else Pushup(u);
            u = fa[u];
        }
        return;
    }
} bst;

inline void DFS1(int u, int f)
{
    si[u] = 1, fa[u] = f;
    for(register int i = hd[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(v == f) continue;
        DFS1(v, u), si[u] += si[v];
        if(si[v] > si[hs[u]]) hs[u] = v;
    }
    return;
}

inline int DFS2(int u, int tp)
{
    if(hs[u]) DFS2(hs[u], tp);
    bst.val[u].v[0][0] = 0, bst.val[u].v[1][0] = a[u];
    for(register int i = hd[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(v == fa[u] || v == hs[u]) continue;
        int k = DFS2(v, v);
        bst.fa[k] = u;
        bst.val[u].v[0][0] += max(bst.sum[k].v[0][0], bst.sum[k].v[1][0]);
        bst.val[u].v[1][0] += bst.sum[k].v[0][0];
    }
    bst.val[u].v[0][1] = bst.val[u].v[0][0];
    if(u == tp)
    {
        len = 0; int v = u;
        while(v) seq[++len] = v, v = hs[v];
        return bst.Build(1, len);
    }
    return 0;
}

int main()
{
    #ifdef FILE
        freopen("Input.in", "r", stdin);
        freopen("Output.out", "w", stdout);
    #endif
    scanf("%d%d", &n, &m);
    for(register int i = 1; i <= n; i++) scanf("%d", a + i);
    for(register int i = 1; i < n; i++)
    {
        int u, v; scanf("%d%d", &u, &v);
        Link(u, v), Link(v, u);
    }
    DFS1(1, 0), bst.sum[0].v[0][0] = bst.sum[0].v[1][1] = 0, bst.sum[0].v[0][1] = bst.sum[0].v[1][0] = -INF, bst.fa[bst.rt = DFS2(1, 1)] = 0;
    while(m--)
    {
        int u, w; scanf("%d%d", &u, &w), u ^= Lastans;
        bst.Modify(u, w), printf("%d\n", Lastans = max(bst.sum[bst.rt].v[0][0], bst.sum[bst.rt].v[1][0]));
    }
    return 0;
}

E N D \mathit{END} END

【学习笔记】浅谈广义矩阵乘法——动态DP 竟然有一篇学习博客没有放出来,哎呀呀! 阅读详情

相关推荐

告别浏览器崩溃!EasyPlayer.js长时间播放FLV视频内存泄漏排查指南

本文针对EasyPlayer.js长时间播放FLV视频导致浏览器内存泄漏和崩溃的问题,提供了一套系统性排查与解决方案。核心策略包括将视频编码从H.265转为H.264以降低解码压力,以及将传输协议从HTTP-FLV切换至WebRTC以优化资源管理。文章还详细介绍了播放器实例生命周期管理、浏览器开发者工具监控等精细化排查手段,帮助开发者实现稳定、长时间的多路视频播放。

weixin_29229055的博客 173

动态DP入门&线性动态DP

OI-WiKi上有一个动态DP讲解,直接讲到了树型DP领域,同时需要树链剖分,门槛有点高。本文针对线性DP做一个动态DP的讲解。首先当然要懂得一定的DP的相关知识,然后需要知道DP方程的矩阵表达。可以看这里——根据递推公式构造系数矩阵用于快速幂。很多DP的状态转移方程都可以写成矩阵形式,由此就有了矩阵快速幂优化和动态DP的基础。特别是本文专门举例的线性DP。

RBS的专栏 2130

重带电粒子的能量歧离(energy straggling)

能量歧离,Bethe-Bloch

G751888629的博客 2298

动态规划【DP】详细解释

动态规划算法的复杂度和空间利用率很高,适用于求解一些比较复杂的最优化问题,例如背包问题、最长递增子序列问题、编辑距离问题等。无后效性:状态只与其之前的状态有关,和之后的状态无关。这种性质被称作“无后效性”,即在求解阶段,我们不必关心状态是如何转移过来的,只需要关注现在的状态和后续状态。(除去起点)中的所有点恰好一次,回到起点的最小代价,通过循环方式依次求出每一个状态并存储它的最小代价值,最终返回从起点出发,经过点集。好的,再举一个十分复杂的例子,以解决旅行家问题为例子,我们来详细介绍动态规划的思想和步骤。

Chey_LER_SLC的博客 2234

一篇NS SAR ADC的学习记录

一篇NS SAR ADC的学习记录 内容包含: OverSampling Noise Shaping Dynamic Amplifier DAC with Redundancy 最近看了些有意思的SAR ADC结构,但是因为确实对模拟这个领域不太熟,导致了一系列的级联查找,最终达到了一个大概能懂的状态,为了理清思路,顺便别让看了这么些天的东西浪费了,还是计划写篇文章。 ps:由于不是一篇特别正紧的报告,参考文献就写的比较随便,且我还是个做数字的 A 13.5-ENOB, 107-μW Noise-Sha

weixin_45669724的博客 8710

YOLOv8改进策略【卷积层】| DEConv: 强度信息与梯度特征融合的细节增强 适用于小目标与细节目标检测任务

本文记录的是利用 模块优化 的目标检测网络模型。(Detail-Enhanced Convolution,细节增强卷积)的设计旨在,解决 目标检测网络中传统普通卷积缺乏目标细节相关先验、对小目标边缘、目标与背景边界等关键细节捕捉能力不足的问题,同时避免引入额外参数量与计算开销,以适配 对实时推理性能与检测精度的双重需求。DEA-Net: Single image dehazing based on detail-enhanced convolution and content-guided atte

Limiiiing的博客 348

C++ 动态 DP(Dynamic DP)详解:从入门到实战

动态DP(DynamicDP)是一种结合动态规划和数据结构优化的高级算法,用于解决带动态修改的DP问题。它通过将DP状态转移转化为可维护的线性形式(如矩阵乘法),利用树链剖分和线段树等数据结构实现高效更新。以树上最大独立集为例,动态DP能在修改节点权值时,将单次操作时间复杂度从O(n)优化到O(log²n)。该算法适用于需要频繁修改参数的树形DP问题,如动态维护树上最长路径或子树和等场景。核心实现包括转移矩阵定义、树链剖分预处理和线段树维护,将整体时间复杂度从O(nq)优化到O(n+qlog²n)。

as673456的博客 1433

把数据库事务边界握在手里:ABAP 里的 COMMIT CONNECTION 与 ROLLBACK CONNECTION 深度拆解

与表面是两条很短的语句,背后却牵动了连接管理、数据库 LUW、SAP LUW、隐式提交、游标生命周期、GTT 平台差异等一整片工程现实。把它们用对,你就能在不破坏主业务事务的前提下,实现跨库写入、审计落库、隔离型日志、数据集成落地等一系列“看似矛盾”的目标;用错了,它们也会悄无声息地制造一致性缺口,让问题在高峰期以 dump 或数据异常的方式爆出来。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1837

动态dp(ddp)

详细讲解动态dp。

wanglianda2007的博客 709

Challenge 4 - OSCP A

在c盘下发现一个windows.old,在里面可以找到SYSTEM和SAM文件,下载到本地,利用samdump2解密发现没解出来,再来试试pwdump.py(https://github.com/CiscoCXSecurity/creddump7)得到压缩包密码bludecode,解压之后发现一个比较特殊的文件configuration.php,里面有两个类似密码的字符串。登录之后在/opt/backups/下发现几个特殊文件,其中sitebackup3.zip是个正常的压缩包。但是这个压缩包是有密码的。

羽的博客 5777

3步搞定.NET Core在UOS上的部署?手把手教你从零到上线!

本文详细介绍了如何在统信UOS系统上部署.NET Core应用程序的完整流程。首先,通过安装.NET Core SDK,确保UOS系统能够编译和运行C#代码。接着,创建一个简单的控制台应用,并验证其本地运行情况。最后,通过发布应用、传输文件到UOS服务器,并解决权限问题,确保应用能够在UOS上顺利运行。文章还提供了常见问题的解决方案,如依赖缺失、权限不足和服务启动失败等,帮助开发者避免常见陷阱。通过本文的指导,开发者可以轻松将.NET Core应用部署到UOS系统,并实现24/7的稳定运行。

java专栏 587

【算法竞赛学习笔记】动态DP入门(附模板)

title : 动态DP tags : ACM,动态规划 dates : 2022-2-27 author : Linno 动态DP 动态DP,是将DP转移方程写为矩阵形式后运用数据结构维护来减少更新信息以及操作次数的优秀算法。通常可以配合线段树/平衡树/分块等维护序列信息或者用来解决树上的DP问题,同时支持点权(边权)修改操作。对于每一次修改,都不需要重新计算树上每个结点的答案,而是更新路径上相关点的答案,以均摊O(logn)O(logn)O(logn)的复杂度完成修改。 前置知识 矩阵 / 树链剖分.

SC_Linno的博客 664

【ArcGIS微课1000例】0149:Geographic Imager6.6下载安装教程

首先保证电脑上安装对应版本的PS软件,笔者安装的是PS2023(V24.7)版本,然后安装本软件。保持以上窗口运行状态,然后打开PS,自动弹出如下窗口。勾选【Automatically checkout LIcense】,点击Close。双击setup开始安装,点击【确认】,一路默认安装到底。驱动器:DVD-ROM驱动器(用于DVD版本)处理器:2GHz或更快的英特尔或AMD处理器。安装完成后,将Crack中的所有文件拷贝到。硬盘:600 MB的可用硬盘空间用于安装。内存:4GB内存(建议8GB或更高)。

智慧空间实验室 1217

AcWing算法学习笔记:动态规划(背包 + 线性dp + 区间dp + 计数dp + 状态压缩dp + 树形dp + 记忆化搜索)

若体积从小到大进行遍历,当更新f[i, j]时,f[i - 1, j - vi] 已经在更新f[i, j - vi]时被更新了。因此体积需要从大到小进行遍历,当更新f[i, j]时,f[i - 1, j - vi] 还未被更新。并枚举每一个数字,若以该数字结尾的序列,其倒数第二个数字应该是小于该数字的最大的数。更新状态时,记录每一位子串是由哪一位结尾的子串转移而来的。将f[i]与f[i - 1]轮流交替使用数组进行存储。枚举每一个分组内的物品,进行01背包的选法策略。由于不存在01背包中的更新覆盖的情况。

一只可爱的小猴子的博客 1659

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

动态 DP 学习笔记

不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果。 动态 DP 主要用于解决一类问题。这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作。举个例子,我们来看一道例题。 【模板】动态 DP 给定一棵 \(n\) 个点的树。\(i\) 号点的点权为 \(a_i\)。有 \(m\) 次操作,每次操作给定 \(u, w\),表示修改点 \(u\) 的...

weixin_30614109的博客 201

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

动态dp学习笔记

我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好。 给定一棵n个点的树,点带点权。 有m次操作,每次操作给定x,y,表示修改点x的权值为y。 你需要在每次操作之后求出这棵树的最大权独立集的权值大小。 如果不带修改,那就是一个最简单是树形dp问题。 我们设一个dp[i][0],dp[i][1]表示以i为根...

weixin_33998125的博客 168

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

动态DP学习笔记

动态DP学习笔记 动态DP实际上就是利用树链剖分的性质快速的维护\(f\)和\(g\),利用矩阵乘法和线段树加速修改过程 \(f\)数组的用途在本质上与LCT动态维护树的重心那个轻儿子子树的用途相同,用来“填”重链维护中留下的坑 水平不行 说不清楚。。。 还是代码最直观。。。 注意矩阵乘法不满足交换律 维护线段树时要注意一下从哪边向哪边合并 还有矩阵乘法最后要return 写函数变量不要重名!!...

weixin_30551947的博客 140

[总结] 动态DP学习笔记

学习了一下动态DP 问题的来源: 给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小。 首先一个显然的 \(O(nm)\) 的做法就是每次做一遍树形DP(这也是我在noip考场上唯一拿到的部分分),直接考虑如何优化这个东西。 简化一下问题,假如这棵树是一条链,那就变得很简单了,可以直接拿线段树维护矩阵加速。 可是如果每个点不止有一...

weixin_30950887的博客 84

洛谷4719 【模板】动态dp 学习笔记(ddp 动态dp)

qwq大概是混乱的一个题。 首先,还是从一个比较基础的想法开始想起。 如果每次暴力修改的话,那么每次就可以暴力树形dp 令dp[x][0/1]dp[x][0/1]dp[x][0/1]表示xxx的子树中,是否选择xxx这个点的最大权独立集。 如果这个点不选,那么他的所有儿子都是可以选的。 如果这个点选的,那么只能加上他的所有儿子不选的收益。 因为收益可能存在负数,所以要特别处理一下 void dfs...

y_immortal的博客 QwQ(qdez_ymh) 429

学习笔记——动态DP

一直觉得DDP是一个神奇的东东,直到放弃了保卫王国的神奇倍增法之后才开始学习DDP 模板题: 给定一颗点带权的树,有\(m\)次修改,每次修改一个点的权值,要求在每次修改之后输出整棵树的最大权独立集的权值大小\((n,m\leq 10^5)\) 暴力DP 首先很容易得到没有修改操作时的dp方程(即没有上司的舞会) 设\(f_{i,1}\)表示选\(i\),\(f_{i,0}\)表示不选...

weixin_30767921的博客 197

【学习笔记】动态DP

引子 如果把修改扔开,这就是一道十分经典的树形DP入门题。 然而加上修改,这道题瞬间毒瘤了起来。 先把转移方程摆在下面: f[i][0]=∑j∈sonmax(f[j][0],f[j][1])f[i][1]=∑j∈sonf[j][0]+w[i]f[i][0]=\sum_{j \in son}max(f[j][0],f[j][1]) \\ f[i][1]=\sum_{j \in son} f[j]...

C20181503csy的博客 310

NOIP学习笔记——动态规划dp基础

本文介绍了动态规划的基本概念、应用条件和解题要点,并通过多个例题展示了动态规划在求解最值问题和方案数问题中的应用。文章首先明确了动态规划的两个必要条件:最优子结构和无后效性,强调状态转移图必须是有向无环图。然后详细讲解了动态规划问题的五个关键要素:状态定义、转移方程、初始化、枚举顺序和答案表示。通过斐波那契数列、门票问题、数字三角形等经典例题,展示了如何构建状态转移方程。还特别针对后效性问题提出了两种解决方法:移项法和多步等效法。最后通过放苹果、吃糖果等较复杂的例题,说明了状态转移方程的构建技巧。文章结构清

fhhhcc的博客 1086

数据结构与算法学习笔记(Acwing 提高课)----动态规划·数位DP

@ author: 明月清了个风ps⭐️数位DP是比较有特点的DP问题,难度较高,细节会比较多,但是题目之间的类似性非常高,已经讲过一道题了,难度也比较大,可以先复习一下。

weixin_60278491的博客 1308
上一篇: 平衡树学习笔记
下一篇: P4224 [清华集训2017]简单数据结构 题解
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值