学习笔记——动态DP

mac2022-06-30  22

一直觉得DDP是一个神奇的东东,直到放弃了保卫王国的神奇倍增法之后才开始学习DDP

模板题:

给定一颗点带权的树,有\(m\)次修改,每次修改一个点的权值,要求在每次修改之后输出整棵树的最大权独立集的权值大小\((n,m\leq 10^5)\)


暴力DP

首先很容易得到没有修改操作时的dp方程(即没有上司的舞会)

\(f_{i,1}\)表示选\(i\)\(f_{i,0}\)表示不选\(i\)时的最大权独立集权值大小

\(f_{i,0}=\Sigma{max(f_{v,1},f_{v,0})}\)

\(f_{i,1}=\Sigma{f_{v,0}}+a_i\)


矩阵优化

先将树进行重链剖分

\(g_{i,1}\)表示选择\(i\)\(g_{i,0}\)表示不选\(i\)\(i\)不在重链上的儿子的\(f\)之和(即不算重儿子,\(g\)的转移同上)

得到新的转移方程(\(son\)为重儿子):

\(f_{i,0}=max(f_{son,1},f_{son,0})+g_{i,0}\)

\(f_{i,1}=f_{son,0}+g_{i,1}\)

这玩意可以变成矩阵形式

\(\begin{bmatrix} f_{i,0}\\ f_{i,1}\\ \end{bmatrix}=\begin{bmatrix} g_{i,0} & g_{i,0}\\ g_{i,1} & -\infty\\ \end{bmatrix} ×\)\(\begin{bmatrix} f_{son,0}\\ f_{son,1}\\ \end{bmatrix}\)

这里改变了一下矩阵的左乘运算,原来的\(\Sigma{a_{i,k}*b_{k,j}}\) 变成了\(max(a_{i,k}+b_{k,j})\)

由于\(+\)\(max\)同样满足\(*\)\(+\)的运算性质,所以这样替换后的矩阵乘法仍然满足之前的性质

于是将每一个点i变成一个矩阵

\(\begin{bmatrix} g_{i,0} & g_{i,0} \\ g_{i,1} & -\infty \\ \end{bmatrix}\)

修改点权变成了修改矩阵,用一颗线段树来维护这些矩阵的乘积


求解

\(ans=max(f_{i,0},f_{i,1})\),重链剖分之后,\(ans=query(1,end_{1})\),end表示一条重链的终点(这条重链会从1号节点出发一直到某个叶子节点,而叶子节点没有儿子,在上面的式子中就相当于没有f矩阵,所以直接等于g矩阵),可以发现求\(ans\)的过程与\(f\)无关,所以我们成功的将\(f\)数组扔掉了,之后的修改操作只与矩阵有瓜

修改一个点\(I\)的权值,会导致\(g_{i,1}\)改变,但是由于与它在同一条重链上的父亲的\(g\)值与\(i\)无关(\(g\)的定义说明了\(g\)值与重链上的点无关),我们不会修改这些点。由此还可以看出,我们只会修改两条重链交界的点,也就是每个\(fa[top[i]]\),由于重链只有\(log\)条,所以只会单点修改\(log\)次(这一段的思路和大部分树链剖分优化DP相同)

整个算法的时间复杂度就是\(O(2^3*nlog^2n)\)

代码略长,因为是小萌新oier

Code

#include<bits/stdc++.h> #define N 100005 #define Max(x,y) ((x)>(y)?(x):(y)) #define Min(x,y) ((x)<(y)?(x):(y)) using namespace std; typedef long long ll; const ll INF = 100000000000000; int n,m; int seg[N],rev[N],top[N],end[N],size[N],son[N],fa[N],dep[N],hfu; ll a[N],f[N][2]; struct Matrix { ll g[2][2]; Matrix () { memset(g,0,sizeof(g)); } Matrix operator * (const Matrix &a)const { Matrix c; for(int i=0;i<2;++i) for(int j=0;j<2;++j) for(int k=0;k<2;++k) c.g[i][j]=Max(c.g[i][j],g[i][k]+a.g[k][j]); return c; } }t[N<<2],val[N]; struct Edge { int next,to; }edge[N<<1];int head[N],cnt=1; void add_edge(int from,int to) { edge[++cnt].next=head[from]; edge[cnt].to=to; head[from]=cnt; } template <class T> void read(T &x) { char c;int sign=1; while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48; while((c=getchar())>='0'&&c<='9') x=x*10+c-48; x*=sign; } void dfs1(int rt) { size[rt]=1; dep[rt]=dep[fa[rt]]+1; for(int i=head[rt];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[rt]) continue; fa[v]=rt; dfs1(v); size[rt]+=size[v]; if(size[v]>size[son[rt]]) son[rt]=v; f[rt][0]+=Max(f[v][0],f[v][1]); f[rt][1]+=f[v][0]; } } void dfs2(int rt) { if(son[rt]) { seg[son[rt]]=++hfu; rev[hfu]=son[rt]; top[son[rt]]=top[rt]; end[top[rt]]=son[rt]; //还要搞一个重链的结尾位置 dfs2(son[rt]); } for(int i=head[rt];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[rt]||v==son[rt]) continue; seg[v]=++hfu; rev[hfu]=v; top[v]=v; end[v]=v; dfs2(v); } } void update(int rt,int l,int r,int x) { if(l==r) { t[rt]=val[l]; return; } int mid=(l+r)>>1; if(x<=mid) update(rt<<1,l,mid,x); else update(rt<<1|1,mid+1,r,x); t[rt]=t[rt<<1]*t[rt<<1|1]; } Matrix query(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) return t[rt]; int mid=(l+r)>>1; if(x<=mid&&y<=mid) return query(rt<<1,l,mid,x,y); if(x>mid&&y>mid) return query(rt<<1|1,mid+1,r,x,y); return query(rt<<1,l,mid,x,y)*query(rt<<1|1,mid+1,r,x,y); } void build(int rt,int l,int r) { if(l==r) { int u=rev[l]; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[u]||v==son[u]) continue; t[rt].g[0][0]+=Max(f[v][0],f[v][1]); t[rt].g[1][0]+=f[v][0]; } t[rt].g[0][1]=t[rt].g[0][0]; t[rt].g[1][0]+=a[u]; val[l]=t[rt]; return; } int mid=(l+r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); t[rt]=t[rt<<1]*t[rt<<1|1]; } void modify_edge(int rt,ll w) { val[seg[rt]].g[1][0]+=w-a[rt]; a[rt]=w; Matrix las,now; while(rt) { las=query(1,1,n,seg[top[rt]],seg[end[top[rt]]]); update(1,1,n,seg[rt]); now=query(1,1,n,seg[top[rt]],seg[end[top[rt]]]); rt=fa[top[rt]]; val[seg[rt]].g[0][0]+=Max(now.g[0][0],now.g[1][0])-Max(las.g[0][0],las.g[1][0]); val[seg[rt]].g[0][1]=val[seg[rt]].g[0][0]; val[seg[rt]].g[1][0]+=now.g[0][0]-las.g[0][0]; } } int main() { read(n);read(m); for(int i=1;i<=n;++i) read(a[i]),f[i][1]=a[i]; for(int i=1;i<n;++i) { int x,y; read(x);read(y); add_edge(x,y); add_edge(y,x); } seg[1]=rev[1]=top[1]=hfu=1; dfs1(1); dfs2(1); build(1,1,n); while(m--) { int x; ll y; read(x);read(y); modify_edge(x,y); Matrix ans=query(1,1,n,seg[1],seg[end[1]]); printf("%lld\n",Max(ans.g[0][0] , ans.g[1][0])); } return 0; }

cv一遍就可以将保卫王国过了,强制选或不选就等价于将某个点的点权赋为(-)INF,最小点权覆盖集=全集-最大点权独立集

转载于:https://www.cnblogs.com/Chtholly/p/11593851.html

相关资源:JAVA上百实例源码以及开源项目
最新回复(0)