为了能到远方,脚下的每一步都不能少.|

Aurora-JC

园龄:3年1个月粉丝:3关注:4

2023-08-31 22:06阅读: 24评论: 0推荐: 0

*【学习笔记】(3) 动态DP

动态 DP 简称 DDP(Dynamic Dynamic Programming),其本质是用 矩阵 维护带修改的动态规划问题。

1.算法介绍:树链剖分写法

模板:P4719 【模板】"动态 DP"&动态树分治

给定一棵 n 个点的树。i 号点的点权为 ai 。有 m 次操作,每次操作给定 u,w,表示修改点 u 的权值为 w。你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

广义 矩阵乘法

Ci,j=k=1nAi,kBk,j
只需满足 具有 结合律,且 有 分配律,则存在结合律。

不带修改操作

若没有修改操作,本题是经典的 树上最大独立集 问题,详见没有上司的舞会。设 fi,0/1 分别表示 不选 或 选 点 i 的最大权值,有
fi,0=xsonimax(fx,0,fx,1)
fi,1=ai+xsonifx,0

首先根据动态规划的转移方程可以发现,我们修改了一个点的点权,只会更改从这个点到根这条路径上节点的 DP 值,其他值是不会发生更改的。这时候如果我们要对整棵树重新求一遍最大权独立集,难免会 T。所以我们希望能够更改这条链上的 DP 值。

想在树上一条链上进行快速修改,无疑我们可以使用树链剖分。

首先,对树 进行树链剖分,记 mxii 的重儿子。

我们保持 f 数组的定义不变。为了迎合重链剖分划分出了轻重儿子,我们定义g 数组:gi,1 表示 i 号点的所有轻儿子,都不取的最大权独立集;gi,0 表示 i 号点的所有轻儿子,可取可不取形成的最大权独立集。将 DP 式子简化成:

fi,0=gi,0+max(fj,0,fj,1)

fi,1=gi,1+ai+fj,0

ji 的重儿子。对于叶子节点 gi,0=gi,1=0
感觉不太优美,将 gi,1ai 合并起来。得到 gi,1 新的定义 :表示 i 号点只考虑轻儿子的取自己的最大权独立集。那么这时候,第二个方程就可以变为 fi,1=gi,1+fj,0​。

因为不满足结合律,想办法构造矩阵用矩阵快速幂来加速。

考虑维护一个 1×2 的矩阵

fi,0     fi,1

现在我们要从一个点的重儿子 j 转移到 i 上,也就是说我们需要构造出一个转移矩阵使得 |fj,0fj,1|​ 能够转移到 |fi,0fi,1|

新定义一个运算符 ,对于矩阵 A,B, AB结果 C 满足:

Ci,j=max(Ai,k+Bk,j)

FJC operator * (const FJC &B){
FJC res; res.clear();
for(int i = 1; i <= 2; ++i)
for(int j = 1; j <= 2; ++j)
for(int k = 1; k <= 2; ++k)
res.c[i][j] = max(res.c[i][j], c[i][k] + B.c[k][j]);
return res;
}

至于为什么这个具有结合律。一种感性的理解:由于 max 操作和加法操作都是满足结合率的,所以这个运算满足结合率。

将转移方程做个变形:

fi,0=max(gi,0+fj,0,gi,0+fj,1)

fi,1=max(gi,1+ai+fj,0,)

最后得到

|fj,0fj,1||gi,0gi,1gi,0|=|fi,0fi,1|

这样子,我们对于一条重链,我们的叶子节点就存储了最初始的值,链上每个节点都对应着一个转移矩阵。我们发现这个转移矩阵和重链信息是没有任何关系的,且因为这个矩阵满足结合率,对于一条重链,我们可以之间线段树维护区间“*”积。然后直接跳到了一条重链链头,因为这个点是它父亲的轻儿子,我们需要更新它父亲节点所在的点的转移矩阵。这样子一直跳到根节点就可以了。
重链剖分剖出的 DFS 序,由于先访问了链头,所以这个区间中,链头在区间左端,链尾在区间右端。我们存储的初始信息在叶子节点(也就是链尾)上,因此我们的矩阵 法应当是转移矩阵在前,要维护的值矩阵在后。我们要把这个矩阵前后换个顺序,再转个个儿,加上一些推算,可以变形成:

|gi,0gi,0gi,1||fj,0fj,1|=|fi,0fi,1|

#include<bits/stdc++.h>
#define N 100005
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
int read(){
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return x * f;
}
struct FJC{
int c[3][3];
void clear(){memset(c, 0xcf, sizeof(c));}
FJC operator * (const FJC &B){
FJC res; res.clear();
for(int i = 1; i <= 2; ++i)
for(int j = 1; j <= 2; ++j)
for(int k = 1; k <= 2; ++k)
res.c[i][j] = max(res.c[i][j], c[i][k] + B.c[k][j]);
return res;
}
}opt[N];
int n, m, tot, t;
int Head[N], to[N << 1], Next[N << 1];
int a[N], fa[N], d[N], sz[N], maxson[N];
int dfn[N], top[N], ed[N], f[N][2], id[N];
void add(int u, int v){
to[++tot] = v, Next[tot] = Head[u], Head[u] = tot;
}
struct Segment{
FJC v[N << 2];
void PushUp(int u){
v[u] = v[ls] * v[rs];
}
void Build(int u, int l, int r){
if(l == r) return v[u] = opt[id[l]], void();
int mid = (l + r) >> 1;
Build(ls, l, mid), Build(rs, mid + 1, r);
PushUp(u);
}
void UpDate(int u, int l, int r, int x){
if(l == r) return v[u] = opt[id[l]], void();
int mid = (l + r) >> 1;
if(x <= mid) UpDate(ls, l, mid, x);
else UpDate(rs, mid + 1, r, x);
PushUp(u);
}
FJC Query(int u, int l, int r, int L, int R){
if(L <= l && r <= R) return v[u];
int mid = (l + r) >> 1;
if(R <= mid) return Query(ls, l, mid, L, R);
else 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);
}
}tr;
void dfs1(int x){
sz[x] = 1;
for(int i = Head[x]; i; i = Next[i]){
int y = to[i]; if(y == fa[x]) continue;
fa[y] = x, d[y] = d[x] + 1, dfs1(y);
sz[x] += sz[y]; if(sz[y] > sz[maxson[x]]) maxson[x] = y;
}
}
void dfs2(int x, int topf){
dfn[x] = ++t, id[t] = x, top[x] = topf, ed[topf] = max(ed[topf], t);
f[x][0] = 0, f[x][1] = a[x];
opt[x].clear();
opt[x].c[1][1] = opt[x].c[1][2] = 0;
opt[x].c[2][1] = a[x];
if(!maxson[x]) return ;
dfs2(maxson[x], topf);
f[x][0] += max(f[maxson[x]][0], f[maxson[x]][1]);
f[x][1] += f[maxson[x]][0];
for(int i = Head[x]; i; i = Next[i]){
int y = to[i]; if(y == fa[x] || maxson[x] == y) continue;
dfs2(y, y); f[x][0] += max(f[y][0], f[y][1]), f[x][1] += f[y][0];
opt[x].c[1][2] = (opt[x].c[1][1] += max(f[y][0], f[y][1]));
opt[x].c[2][1] += f[y][0];
}
}
void UpDate_Path(int x, int y){
opt[x].c[2][1] += y - a[x], a[x] = y;
FJC lst, now;
while(x){
lst = tr.Query(1, 1, n, dfn[top[x]], ed[top[x]]);
tr.UpDate(1, 1, n, dfn[x]);
now = tr.Query(1, 1, n, dfn[top[x]], ed[top[x]]);
x = fa[top[x]];
opt[x].c[1][2] = (opt[x].c[1][1] += max(now.c[1][1], now.c[2][1]) - max(lst.c[1][1], lst.c[2][1]));
opt[x].c[2][1] += now.c[1][1] - lst.c[1][1];
}
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
add(u, v), add(v, u);
}
dfs1(1), dfs2(1, 1);
tr.Build(1, 1, n);
for(int i = 1; i <= m; ++i){
int x = read(), y = read();
UpDate_Path(x, y);
FJC ans = tr.Query(1, 1, n, dfn[1], ed[1]);
printf("%d\n", max(ans.c[1][1], ans.c[2][1]));
}
return 0;
}

例题

Ⅰ. P5024 [NOIP2018 提高组] 保卫王国

板题,最小权覆盖集 = 全集 - 最大权独立集,不得驻扎将点权加上正无穷,否则反之。

点击查看代码
#include<bits/stdc++.h>
#define ls u << 1
#define rs u << 1 | 1
#define INF 1e9
#define ll long long
using namespace std;
const int N = 2e5 + 67;
int read(){
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
return x * f;
}
struct FJC{
ll c[3][3];
void clear(){memset(c, 0xcf, sizeof(c));}
FJC operator * (const FJC B){
FJC res; res.clear();
for(int i = 1; i <= 2; ++i)
for(int j = 1; j <= 2; ++j)
for(int k = 1; k <= 2; ++k)
res.c[i][j] = max(res.c[i][j], c[i][k] + B.c[k][j]);
return res;
}
}op[N];
int n, m, opt, tot, cnt;
ll sum;
int a[N], sz[N], fa[N], d[N], son[N];
int dfn[N], top[N], id[N], ed[N];
ll f[N][2];
int Head[N], to[N], Next[N];
void add(int u, int v){
to[++tot] = v, Next[tot] = Head[u], Head[u] = tot;
}
struct Segment{
FJC v[N << 2];
void pushup(int u){
v[u] = v[ls] * v[rs];
}
void build(int u, int l, int r){
if(l == r) return v[u] = op[id[l]], void();
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
pushup(u);
}
void update(int u, int l, int r, int x){
if(l == r) return v[u] = op[id[l]], void();
int mid = (l + r) >> 1;
if(x <= mid) update(ls, l, mid, x);
else update(rs, mid + 1, r, x);
pushup(u);
}
FJC query(int u, int l, int r, int L, int R){
if(L <= l && r <= R) return v[u];
int mid = (l + r) >> 1;
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);
}
}tr;
void dfs1(int x){
sz[x] = 1;
for(int i = Head[x]; i; i = Next[i]){
int y = to[i]; if(y == fa[x]) continue;
fa[y] = x, d[y] = d[x] + 1, dfs1(y);
if(sz[y] > sz[son[x]]) son[x] = y;
sz[x] += sz[y];
}
}
void dfs2(int x, int topf){
dfn[x] = ++cnt, id[cnt] = x, top[x] = topf, ed[topf] = max(ed[topf], cnt);
f[x][0] = 0, f[x][1] = a[x];
op[x].clear();
op[x].c[1][1] = op[x].c[1][2] = 0;
op[x].c[2][1] = a[x];
if(!son[x]) return ;
dfs2(son[x], topf);
f[x][0] += max(f[son[x]][0], f[son[x]][1]);
f[x][1] += f[son[x]][0];
for(int i = Head[x]; i; i = Next[i]){
int y = to[i]; if(y == fa[x] || y == son[x]) continue;
dfs2(y, y);
f[x][0] += max(f[y][0], f[y][1]), f[x][1] += f[y][0];
op[x].c[1][2] = (op[x].c[1][1] += max(f[y][0], f[y][1]));
op[x].c[2][1] += f[y][0];
}
}
void update_path(int x, ll y){
op[x].c[2][1] += y;
FJC lst, now;
while(x){
lst = tr.query(1, 1, n, dfn[top[x]], ed[top[x]]);
tr.update(1, 1, n, dfn[x]);
now = tr.query(1, 1, n, dfn[top[x]], ed[top[x]]);
x = fa[top[x]];
op[x].c[1][2] = (op[x].c[1][1] += max(now.c[1][1], now.c[2][1]) - max(lst.c[1][1], lst.c[2][1]));
op[x].c[2][1] += now.c[1][1] - lst.c[1][1];
}
}
int main(){
n = read(), m = read(), opt = read();
for(int i = 1; i <= n; ++i) a[i] = read(), sum += a[i];
for(int i = 1; i < n; ++i){
int u = read(), v = read();
add(u, v), add(v, u);
}
dfs1(1), dfs2(1, 1);
tr.build(1, 1, n);
while(m--){
int x = read(), stx = read();
int y = read(), sty = read();
if(stx == 0 && sty ==0 && (fa[x] == y || fa[y] == x)){
printf("-1\n"); continue;
}
update_path(x, stx ? -INF : INF);
update_path(y, sty ? -INF : INF);
sum += ((stx ^ 1) + (sty ^ 1)) * INF;
FJC ans = tr.query(1, 1, n, dfn[1], ed[1]);
printf("%lld\n", sum - max(ans.c[1][1], ans.c[2][1]));
update_path(x, stx ? INF : -INF);
update_path(y, sty ? INF : -INF);
sum -= ((stx ^ 1) + (sty ^ 1)) * INF;
}
return 0;
}

Ⅱ. P6573 [BalticOI 2017] Toll

Ⅲ. P7359 「JZOI-1」旅行

参考资料:OI WiKiDP 优化方法大杂烩 I.
Tweetuzki 的题解

本文作者:Aurora-JC

本文链接:https://www.cnblogs.com/jiangchen4122/p/17417620.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Aurora-JC  阅读(24)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起