动态DP,题解
动态DP
\({\huge 动态DP}\)是一种 \(DP\) 优化
链上动态DP
例子
我们考虑一个 \(DP\) :
最大不连续子序列和
显然有转移方程:
定义: \(f_{i,0}\) 表示第 \(i\) 位不取的最大和, \(f_{i,1}\) 表示第 \(i\) 位取的最大和
\(\left\{\begin{matrix}f_{i,0}=\max(f_{i-1,0},f_{i-1,1}) \\f_{i,1}=f_{i-1,0}+a_i \end{matrix}\right.\)
可以看出:这个 \(DP\) 是 \(\mathit{O}(n)\) 的,而且 \(f_i\) 只与 \(f_{i-1}\) 有关
我们可以定义广义矩阵乘法 \(\displaystyle A*B=Matrix(c_{i,j}=\max_k(a_{i,k}+b_{k,j}))\)
则有转移矩阵:
\(\begin{bmatrix}f_{i-1,0}\\f_{i-1,1}\end{bmatrix} \times \begin{bmatrix}0&0\\a_i&-inf\end{bmatrix} = \begin{bmatrix}f_{i,0}\\f_{i,1}\end{bmatrix}\)
有人可能要问了,这个转移矩阵有什么用呢?
我们来考虑下面这个问题:
动态最大不连续子序列和
这个序列变成带修的了,我们有如何做呢?
首先有个朴素的暴力:
每次修改之后重新计算 \(f\) 数组
然而这个是 \(\mathit{O}(qn)\) 的
然而我们可以想到:
假设我们修改 \(a_x\) 为 \(y\) 那么 \(f_{1...x-1}\) 是不用修改的,可以优化一点,但不多
我们考虑到:\(f\) 数组的转移可以通过矩阵乘法,而矩阵又有结合律,所以我们可以通过线段树来优化
复杂度为 \(\mathit{O}((q+n)\log n)\) 的
例题:
题面:
你有一个高度为 \(1\) ,长度为 \(n\) 的画布,其被分成了 \(n\) 个小格。你还有 \(m\) 种颜料。
不同的位置适合使用的颜料是不同的,当第 \(i\) 个小格填入了第 \(j\) 种颜色,该小格的美观度为 \(a_{i,j}\) 。整个画布的美观度为所有小格的美观度的和。
最初画布上已经有颜料了。你可以进行若干次(可以为0次)以下操作:
- 指定 \(1\le i\le j\le n\),使得第 \(i\) 个小格的颜色与第 \(j\) 个小格的颜色相同,然后使用刷子将第 \(i...j\) 小格的颜色全部变为该颜色。
你需要先求出原画布的最大美观度,然后处理 \(q\) 组操作,强制在线,共两类:
- 由宇宙射线的影响,最初画布的第 \(p_i\) 小格的颜色变化为了 \(x_i\) ;
- 求出将第 \(l_i...r_i\) 个小格考虑为新的子画布的情况下,该子画布的最大美观度
数据范围:
\(1\le p_i \le n \le 10^5 , 1 \le x_i,c_i \le m \le 5 , 0 \le q \le 5\times 10^4,1\le a_{i,j}\le 10^9\)
注意到 \(m\) 很小,直接枚举颜色,手玩数据发现操作区间之交为 $\emptyset $。
记 f_{i,j} 表示第 \(i\) 块通过操作染成 \(j\) 色, \(f_{i,0}\) 表示 \(i\) 没有被操作。
则有
\(\left\{\begin{matrix}
{\color{red}f_{i,j}=f_{i-1,j}+a_{i,j}}
\\{\color{green}f_{i,0}=f_{i-1,0}+a_{i,c[i]}}
\\{\color{blue}f_{i,0}=f_{i-1,c[i-1]}+a_{i,c[i]}}
\\{\color{violet}f_{i,c[i-1]}=f_{i,0}+a_{i,c[i-1]}}
\end{matrix}\right.\)
\({\color{Red}红色行代表 i 处于被覆盖区间中}\)
\({\color{green}绿色行表示 i 与 i-1 均不被覆盖在区间中}\)
\({\color{blue}蓝色行表示 i 位于覆盖区间的后面}\)
\({\color{violet}蓝紫色行表示 i 位于覆盖区间的开头}\)
不妨设 \(c[i-1]=2,c[i]=4\)
构建矩阵得:
\( \begin{bmatrix}f_{i-1,0}\\f_{i-1,1}\\f_{i-1,2}\\f_{i-1,3}\\f_{i-1,4}\\f_{i-1,5}\end{bmatrix} \times \begin{bmatrix}{ \color{green}a_{i,c[i]}}&inf&{\color{blue}a_{i,c[i]}}&inf&inf&inf \\inf&{\color{red}a_{i,1}}&inf&inf&inf&inf \\{\color{violet}a_{i,c[i-1]}}&inf&{\color{red}a_{i,2}}&inf&inf&inf \\inf&inf&inf&{\color{red}a_{i,3}}&inf&inf \\inf&inf&inf&inf&{\color{red}a_{i,4}}&inf \\inf&inf&inf&inf&inf&{\color{red}a_{i,5}} \end{bmatrix}= \begin{bmatrix}f_{i,0}\\f_{i,1}\\f_{i,2}\\f_{i,3}\\f_{i,4}\\f_{i,5}\end{bmatrix} \)
这里 \(f_{i,0}\) 的作用是为了标记覆盖区间,因为子画布的末尾必须为非覆盖区间或区间的右端点
最后我们要求 \(f_{r,0}\) 和 \(f_{r,c[r]}\)
又 \(f_{l}\) 一开始只能是 \(f_{l,0}\)
所以有 \(f_{r,0}=a_{l,c[l]}+M_{0,0}\) 和 \(f_{r,c[r]}=a_{l,c[l]}+M_{c[l],0}\)
树上动态DP
还是和链上一样讨论最大不连续子序列和
不过在树上有个更高级的名字:最大独立集
显然我们能得到 \(DP\) 方程组:
\(\left\{\begin{matrix} \displaystyle f_{u,0}=\sum_{v\in son(u)} \max(f_{v,0},f_{v,1}) \\\displaystyle f_{u,1}=a_i+\sum_{v\in son(u)} f_{v,0} \end{matrix}\right.\)
将其转化为动态问题:
我们发现好像没有链上那么简单了
因为有多个儿子的节点 \(u\) 其的 \(f\) 数组由其所有儿子转移而来,没法线段树维护
但是想到树和线段树,我们马上就想到,我们可以树链剖分
将整颗树转化为数条重链我们可以在这条链上维护 \(f\) 数组
再记 \(g_{u,0}\) 表示对于节点 \(u\) 的所有轻儿子可取可不取的方案, \(g_{u,1}\) 表示节点 \(u\) 所有轻儿子均不取的方案
则有转移方程组:
\(\left\{\begin{matrix} f_{u,0}= g_{u,0} + \max(f_{son[u],0},f_{son[u],1}) \\f_{u,1}=a_u+g{u,1}+f_{son[u],0} \end{matrix}\right.\)
其中: \(son[u]\) 表示 \(u\) 的重儿子
这下我们的转移方程就变成线性的了
可以构造出矩阵:
\(\begin{bmatrix}f_{son[u],0}\\f_{son[u],1}\end{bmatrix}\times\begin{bmatrix}g_{u,0}&g_{u,0}\\a_u+g_{u,1}&-inf\end{bmatrix}=\begin{bmatrix}f_{u,0}\\f_{u,1}\end{bmatrix}\)
我们每次修改,先在对应的重链的线段树上修改,然后再修改重链顶端的父亲,向上递归即可
代码
#include<iostream>
#include<vector>
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define l(u) dfn[tp[u]]
#define r(u) ed[tp[u]]
#define F M[dfn[u]]
using namespace std;
const int N=1e5+5,inf=1e9;
int mmax(int l,int r){
if(l<0&&r<0)return -inf;
if(l<r)return r;
return l;
}
struct Mat{
int a11,a12,a21,a22;
Mat operator * (const Mat t)const{
Mat ret;
ret.a11=mmax(a11+t.a11,a12+t.a21);
ret.a12=mmax(a11+t.a12,a12+t.a22);
ret.a21=mmax(a21+t.a11,a22+t.a21);
ret.a22=mmax(a21+t.a12,a22+t.a22);
return ret;
}
}sum[N<<2],M[N],ans;
int n,m,a[N];
int son[N],siz[N],fa[N],tp[N],dfn[N],DFN,ed[N],f[N][2];
vector<int>g[N];
void build(int p,int l,int r){
if(l==r){
sum[p]=M[l];
return;
}
build(ls,l,mid);build(rs,mid+1,r);
sum[p]=sum[ls]*sum[rs];
}
void modify(int p,int l,int r,int x){
if(l==r){
sum[p]=M[l];
return;
}
if(x<=mid)modify(ls,l,mid,x);
else modify(rs,mid+1,r,x);
sum[p]=sum[ls]*sum[rs];
}
Mat query(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return sum[p];
if(L<=mid&&R>mid)return query(ls,l,mid,L,R)*query(rs,mid+1,r,L,R);
else if(L<=mid)return query(ls,l,mid,L,R);
else return query(rs,mid+1,r,L,R);
}
void dfs1(int u,int ft){
siz[u]=1;
fa[u]=ft;
for(auto v:g[u]){
if(v==ft)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
}
void dfs2(int u,int ft,int x){
dfn[u]=++DFN;
tp[u]=x;
ed[x]=dfn[u];
f[u][0]=0;f[u][1]=a[u];
F.a11=F.a12=0;
F.a21=a[u];F.a22=-inf;
if(son[u]){
dfs2(son[u],u,x);
f[u][0]+=max(f[son[u]][0],f[son[u]][1]);
f[u][1]+=f[son[u]][0];
}
for(auto v:g[u]){
if(v==ft||v==son[u])continue;
dfs2(v,u,v);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
F.a11+=max(f[v][0],f[v][1]);
F.a12=F.a11;
F.a21+=f[v][0];
}
}
void modify_path(int u,int w){
F.a21+=w-a[u];
a[u]=w;
Mat bef,aft;
while(u!=0){
bef=query(1,1,n,l(u),r(u));
modify(1,1,n,dfn[u]);
aft=query(1,1,n,l(u),r(u));
u=fa[tp[u]];
F.a11+=max(aft.a11,aft.a21)-max(bef.a11,bef.a21);
F.a12=F.a11;
F.a21+=aft.a11-bef.a11;
}
}
int main(){int x,y;
cin>>n>>m;
for(int i=1;i<=n;++i)cin>>a[i];
for(int i=1;i< n;++i){
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs1(1,0);dfs2(1,0,1);build(1,1,n);
while(m--){
cin>>x>>y;
modify_path(x,y);
ans=query(1,1,n,1,ed[1]);
cout<<max(ans.a11,ans.a21)<<endl;
}
return 0;
}
例题
[NOIP2018 提高组] 保卫王国
由于这题没有修改点权,可以通过倍增解决
先看转移方程:
\( \left\{\begin{matrix}\displaystyle f_{u,1}=a_{u}+\sum_{v\in son(u)}\max(f_{v,0},f_{v,1}) \\\displaystyle f_{u,0}=\sum_{v\in son(u)}f_{v,1} \end{matrix}\right. \)
看完发现:这不就是上一题的略微改版吗?
得出矩阵:
\(g_{u,0}\) 表示 \(u\) 的轻儿子全部取的方案, \(g_{u,1}\) 表示 \(u\) 的轻儿子可取可不取的方案
\(\begin{bmatrix}f_{son[u],0}\\f_{son[u],1}\end{bmatrix}\times\begin{bmatrix}-inf&g_{u,0}\\g_{u,1}+a_{u}&g_{u,1}+a_{u}\end{bmatrix}=\begin{bmatrix}f_{u,0}\\f_{u,1}\end{bmatrix}\)
考虑该题的修改操作:
- \(u\) 城市必须驻军。若 \(u\) 在链中我们只需要把转移矩阵修改为 \(\begin{bmatrix}-inf&g_{fa[u],0}\\-inf&g_{fa[u],1}+a_{fa[u]}\end{bmatrix}\) ;而若 \(u\) 在链头则更简单,我们把 \(u\) 传给 \(g_{fa[u],0/1}\) 时只传 \(f_{u,1}\) 即可。
- \(u\) 城市不能驻军。同上:若 \(u\) 在链中我们只需要把转移矩阵修改为 \(\begin{bmatrix}-inf&-inf\\g_{fa[u],1}+a_{fa[u]}&-inf\end{bmatrix}\) ;而若 \(u\) 在链头则更简单,我们把 \(u\) 传给 \(g_{fa[u],0/1}\) 时只传 \(f_{u,0}\) 即可。
[SDOI2017] 切树游戏
第一篇黑色题解
题面:
一棵树, \(n\) 个点,每个点有个权值 \(val\) ,定义一颗子树 \(T\) 的价值为其所有点的权值的异或和,即 \(\displaystyle val_T=\bigoplus_{u\in T}val_u\) 。
\(m\) 个操作,共两种:
Change x y
将 \(x\) 节点的权值改为 \(y\)Query T k
求 \(T\) 的所有子树中权值为 \(k\) 的个数
我们先考虑静态问题,显然有个 \(DP\) :
最后的答案即为 \(\sum^{n}_{u=1} f_{u,k}\)
假设我们以及知道了 \(f_{u,k}\) 现在要加入子树 \(v\) 的贡献 \(\displaystyle f_{u,k}=f_{u,k}+\sum_{i\oplus j=k}f_{u,i}f_{v,j}\)
而上面这个式子我们发现是异或卷积的格式,可以通过 \(FWT\) 来求解。
我们将其变换一下,得 \(fwt[f]_{u,k}=fwt[f]_{u,k}+fwt[f]_{u,k}fwt[f]_{v,k}\)
即 \(fwt[f]_{u,k}*=(fwt[f]_{v,k}+1)\)
再定义 \(\displaystyle dp_u=\sum_{v\in subtree(u)}f_v\)
我们所求即为 \(dp_1\)
待完成-------------------------------------