动态 DP

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

带点权的树,每次修改一个点的权值,求树的最大权独立集。

\(1\le n,m \le 10^5\),点权的绝对值 \(\le 10^2\).

若不带修,先设 \(f_{u,1/0}\) 表示点 \(u\) 选与不选,子树 \(u\) 的最大权独立集。

\[f_{u,0}=\sum_{v}\max(f_{v,0},f_{v,1}) \]

\[f_{u,1}=\sum_{v}f_{v,0}+a_u \]

发现一次修改更新整棵树的复杂度是 \(dep_u\) 的,在链上会被卡到 \(O(n)\).

考虑重剖,对于重链暴力更新。问题在于如何快速修改和查询链的 DP 值。

\(g_{u,1/0}\) 为所有取自己且轻儿子都不取,自己不取且轻儿子可取可不取的最大权独立集。

\[f_{u,0}=g_{u,0}+\max(f_{v,0},f_{v,1}) \]

\[f_{u,1}=g_{u,1}+f_{v,0} \]

\(v\)\(u\) 的重儿子。

构造 \(1\times 2\) 的矩阵

\[\begin{bmatrix}f_{u,0}&f_{u,1}\end{bmatrix} \]

重新定义一个矩阵乘法:

\(C=A * B\),则

\[C_{i,j}=\max_{k}\{A_{i,k}+B_{k,j}\} \]

从重儿子 \(v\) 转移到 \(u\) 身上:

\[f_{u,0}=\max(f_{v,0}+g_{u,0},f_{v,1}+g_{u,0}) \]

\[f_{u,1}=\max(g_{u,1}+f_{v,0},-\infty) \]

已知

\[\begin{bmatrix}f_{v,0}&f_{v,1}\end{bmatrix} * \text{U}=\begin{bmatrix}f_{u,0}&f_{u,1}\end{bmatrix} \]

得到

\[\begin{bmatrix}f_{v,0}&f_{v,1}\end{bmatrix} * \begin{bmatrix}g_{u,0}&g_{u,1}\\g_{u,0}&-\infty\end{bmatrix}=\begin{bmatrix}f_{u,0}&f_{u,1}\end{bmatrix} \]

访问区间的查询是从链头到链尾的,需要维护的信息初始在链尾上,所以应该将答案矩阵放在后面:

\[\begin{bmatrix}g_{u,0}&g_{u,0}\\g_{u,1}&-\infty\end{bmatrix} * \begin{bmatrix}f_{v,0}\\f_{v,1}\end{bmatrix}=\begin{bmatrix}f_{u,0}\\f_{u,1}\end{bmatrix} \]

线段树维护一段区间中矩阵的乘积。

  • 对于一个点的 dp 值,需从该点查询至区间链尾,树剖时多记录一个 \(\text{end}\).

  • \(\text{modify}\) 在函数外记录 \(\text{val}\lbrack p \rbrack\) 表示节点的转移矩阵,避免递归常数过大。

  • \(\text{change}\) 里面要算出未修改个修改后的两个矩阵,然后再修改当前节点的 \(\text{val}\).

  • 这里的 \(\text{query}\) 要换种写法不然会带进去一些奇怪的东西。

时间复杂度 \(O(n\log^2 n)\),加上矩乘的常数。

#include<bits/stdc++.h>
#define N 100010
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
struct Mat{
	int a[2][2];
	Mat(){memset(a,-0x3f,sizeof(a));}
	void clear(){memset(a,0,sizeof(a));}
	Mat operator*(Mat x){
		Mat c;
		for(int i=0;i<2;i++)
			for(int k=0;k<2;k++)
				for(int j=0;j<2;j++)
					c.a[i][j]=max(c.a[i][j],a[i][k]+x.a[k][j]);
		return c;
	}
};
int n,m,a[N];
vector<int>s[N];
int fa[N],siz[N],dep[N],son[N];
int top[N],id[N],dfn[N],ed[N],cnt;
int f[N][2];Mat val[N];
int L[N<<2],R[N<<2];Mat M[N<<2];
#define ls p<<1
#define rs p<<1|1
void update(int p){
	M[p]=M[ls]*M[rs];
}
void build(int p,int l,int r){
	L[p]=l,R[p]=r;
	if(l==r){
		M[p]=val[dfn[l]];
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	update(p);
}
void modify(int p,int x){
	if(L[p]==R[p]){
		M[p]=val[dfn[x]];
		return;
	}
	int mid=(L[p]+R[p])>>1;
	if(x<=mid)modify(ls,x);
	else modify(rs,x);
	update(p);
}
Mat query(int p,int l,int r){
	if(L[p]==l&&R[p]==r)return M[p];
	int mid=(L[p]+R[p])>>1;
	if(r<=mid)return query(ls,l,r);
	if(l>mid)return query(rs,l,r);
	return query(ls,l,mid)*query(rs,mid+1,r);
}
void dfs1(int u){
	siz[u]=1;
	for(int v:s[u]){
		if(dep[v])continue;
		fa[v]=u,dep[v]=dep[u]+1;
		dfs1(v),siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
void dfs2(int u,int t){
	id[u]=++cnt,dfn[cnt]=u;
	top[u]=t,ed[t]=max(ed[t],cnt);
	f[u][0]=0,f[u][1]=a[u];
	val[u].a[0][0]=val[u].a[0][1]=0;
	val[u].a[1][0]=a[u];
	if(son[u]){
		dfs2(son[u],t);
		f[u][0]+=max(f[son[u]][0],f[son[u]][1]);
		f[u][1]+=f[son[u]][0];
	}
	for(int v:s[u]){
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
		f[u][0]+=max(f[v][0],f[v][1]);
		f[u][1]+=f[v][0];
		val[u].a[0][0]+=max(f[v][0],f[v][1]);
		val[u].a[0][1]=val[u].a[0][0];
		val[u].a[1][0]+=f[v][0];
	}
}
void change(int u,int w){
	val[u].a[1][0]+=w-a[u],a[u]=w;
	Mat pre,cur;
	while(u){
		pre=query(1,id[top[u]],ed[top[u]]);
		modify(1,id[u]);
		cur=query(1,id[top[u]],ed[top[u]]);
		u=fa[top[u]];
		val[u].a[0][0]+=max(cur.a[0][0],cur.a[1][0])-max(pre.a[0][0],pre.a[1][0]);
		val[u].a[0][1]=val[u].a[0][0];
		val[u].a[1][0]+=cur.a[0][0]-pre.a[0][0];
	}
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		s[u].push_back(v),s[v].push_back(u);
	}
	dep[1]=1,dfs1(1),dfs2(1,1);
	build(1,1,n);
	for(int u,w;m;m--){
		u=read(),w=read();
		change(u,w);
		Mat ans=query(1,id[1],ed[1]);
		printf("%d\n",max(ans.a[0][0],ans.a[1][0]));
	}
	
	return 0;
}

P5024 [NOIP2018 提高组] 保卫王国

带点权的树,求它的最小权覆盖集。

每次给出 \(a,x,b,y\)\(x=1/0\) 代表 \(a\) 强制选/不选,\(y=1/0\) 代表 \(b\) 强制选/不选。

\(1\le n,m\le 10^5\),点权 \(\in \lbrack 1,10^5\rbrack\).

首先有 最小权覆盖集=全集-最大权独立集

强制选点故在最大权独立集中不存在,设为 \(-\infty\),强制不选设为 \(+\infty\)

不合法即当最小权覆盖集 \(\ge\infty\).

#include<bits/stdc++.h>
#define ll long long
#define N 100010
#define inf 1e12
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
struct Mat{
	ll a[2][2];
	Mat(){a[0][0]=a[0][1]=a[1][0]=a[1][1]=-inf;}
	Mat operator*(Mat x){
		Mat c;
		for(int i=0;i<2;i++)
			for(int k=0;k<2;k++)
				for(int j=0;j<2;j++)
					c.a[i][j]=max(c.a[i][j],a[i][k]+x.a[k][j]);
		return c;
	}
};
int n,m,type;ll A[N],sum;
vector<int>s[N];
int fa[N],siz[N],dep[N],son[N];
int top[N],id[N],dfn[N],ed[N],cnt;
ll f[N][2];Mat val[N];
int L[N<<2],R[N<<2];Mat M[N<<2];
#define ls p<<1
#define rs p<<1|1
void update(int p){
	M[p]=M[ls]*M[rs];
}
void build(int p,int l,int r){
	L[p]=l,R[p]=r;
	if(l==r){
		M[p]=val[dfn[l]];
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	update(p);
}
void modify(int p,int x){
	if(L[p]==R[p]){
		M[p]=val[dfn[x]];
		return;
	}
	int mid=(L[p]+R[p])>>1;
	if(x<=mid)modify(ls,x);
	else modify(rs,x);
	update(p);
}
Mat query(int p,int l,int r){
	if(L[p]==l&&R[p]==r)return M[p];
	int mid=(L[p]+R[p])>>1;
	if(r<=mid)return query(ls,l,r);
	if(l>mid)return query(rs,l,r);
	return query(ls,l,mid)*query(rs,mid+1,r);
}
void dfs1(int u){
	siz[u]=1;
	for(int v:s[u]){
		if(dep[v])continue;
		fa[v]=u,dep[v]=dep[u]+1;
		dfs1(v),siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
void dfs2(int u,int t){
	id[u]=++cnt,dfn[cnt]=u;
	top[u]=t,ed[t]=max(ed[t],cnt);
	f[u][0]=0,f[u][1]=A[u];
	val[u].a[0][0]=val[u].a[0][1]=0;
	val[u].a[1][0]=A[u];
	if(son[u]){
		dfs2(son[u],t);
		f[u][0]+=max(f[son[u]][0],f[son[u]][1]);
		f[u][1]+=f[son[u]][0];
	}
	for(int v:s[u]){
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
		f[u][0]+=max(f[v][0],f[v][1]);
		f[u][1]+=f[v][0];
		val[u].a[0][0]+=max(f[v][0],f[v][1]);
		val[u].a[0][1]=val[u].a[0][0];
		val[u].a[1][0]+=f[v][0];
	}
}
void change(int u,ll w){
	val[u].a[1][0]+=w-A[u],A[u]=w;
	Mat pre,cur;
	while(u){
		pre=query(1,id[top[u]],ed[top[u]]);
		modify(1,id[u]);
		cur=query(1,id[top[u]],ed[top[u]]);
		u=fa[top[u]];
		val[u].a[0][0]+=max(cur.a[0][0],cur.a[1][0])-max(pre.a[0][0],pre.a[1][0]);
		val[u].a[0][1]=val[u].a[0][0];
		val[u].a[1][0]+=cur.a[0][0]-pre.a[0][0];
	}
}
int main(){
	n=read(),m=read(),type=read();
	for(int i=1;i<=n;i++)
		A[i]=read(),sum+=A[i];
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		s[u].push_back(v),s[v].push_back(u);
	}
	dep[1]=1,dfs1(1),dfs2(1,1);
	build(1,1,n);
	for(int a,b,x,y;m;m--){
		a=read(),x=read(),b=read(),y=read();
		ll ta=A[a],tb=A[b];
		change(a,!x?inf:-inf);
		change(b,!y?inf:-inf);
		Mat Ans=query(1,id[1],ed[1]);
		ll ans=max(Ans.a[0][0],Ans.a[1][0]);
		change(a,ta),change(b,tb);
		if(!x)ans-=inf,ans+=A[a];
		if(!y)ans-=inf,ans+=A[b];
		if(sum-ans<inf)printf("%lld\n",sum-ans);
		else printf("-1\n");
	}
	
	return 0;
}

P8820 [CSP-S 2022] 数据传输

草。没绷住。

一棵树,从 \(s\) 出发,不断走到距当前节点不超过 \(k\) 的点,问走到 \(t\),经过的点集 \(\{c\}\) 的最小总点权。

\(1\le n,Q\le 2\times 10^5\)\(1\le k\le 3\).

\(f_i\) 为链上第 \(i\) 个点的 DP 值。

定义矩阵乘法

\[C=A * B\Rightarrow C_{i,j}=\min_{k}\{A_{i,k}+B_{k,j}\} \]

\(k=1\):简单树上差分。

\[f_i=f_{i-1}+v_{a_i} \]

\[\begin{bmatrix}f_{i-1}\end{bmatrix} * \begin{bmatrix}v_{a_i}\end{bmatrix}=\begin{bmatrix}f_i\end{bmatrix} \]

\(k=2\):容易发现 \(c\) 都在链上。

\[f_i=\max(f_{i-1},f_{i-2})+v_{a_i} \]

\[\begin{bmatrix}f_{i-1}&f_{i-2}\end{bmatrix} * \begin{bmatrix}v_{a_i}&0\\v_{a_i}&\infty\end{bmatrix}=\begin{bmatrix}f_i&f_{i-1}\end{bmatrix} \]

\(k=3\):最多向外走一步。

\(f_{i,j}\) 表示在离点 \(i\) 距离为 \(j\) 的点上的最小代价,且\(b_i=\min\limits_{\text{j是i的儿子}}v_j\).

\[f_{i,0}=\min\{f_{i-1,0},f_{i-1,1},f_{i-1,2}\}+v_{a_i} \]

\[f_{i,1}=\min\{f_{i-1,0},f_{i-1,1}+b_{a_i}\} \]

\[f_{i,2}=f_{i-1,1} \]

\[\begin{bmatrix}f_{i-1,0}&f_{i-1,1}&f_{i-1,2}\end{bmatrix} * \begin{bmatrix}v_{a_i}&0&\infty\\v_{a_i}&b_{a_i}&0\\v_{a_i}&\infty&\infty\end{bmatrix}=\begin{bmatrix}f_{i,0}&f_{i,1}&f_{i,2}\end{bmatrix} \]

这不是动态DP这是倍增。

\(D_{i,j},U_{i,j}\)\(i\) 为终点,从上往下/从下往上 \(2^j\) 个点的转移矩阵。预处理时间复杂度 \(O(k^3n\log n)\).

询问拆成 \(s\rightarrow \text{lca}\)\(\text{lca}\)\(\text{lca}\rightarrow t\) 三部分,最后乘起来。

\(\text{lca}\) 可以走到父亲上,修改其转移矩阵中的 \(b_i\).

时间复杂度 \(O(k^3(n+q)\log n)\).

真的难写。

#include<bits/stdc++.h>
#define ll long long
#define N 200010
#define L 19
#define inf 1e18
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
struct Mat{
	int r,c;ll a[3][3];
	Mat(int _r=0,int _c=0):r(_r),c(_c){
		for(int i=0;i<r;i++)
			for(int j=0;j<c;j++)a[i][j]=inf;
	}
	void EMat(){
		for(int i=0;i<r&&i<c;i++)
			a[i][i]=0;
	}
	Mat operator*(Mat x){
		Mat ret(r,x.c);
		for(int i=0;i<r;i++)
			for(int k=0;k<c;k++)
				for(int j=0;j<x.c;j++)
					ret.a[i][j]=min(ret.a[i][j],a[i][k]+x.a[k][j]);
		return ret;
	}
}f0(1,3),D[N][L],U[N][L];
int n,m,k;ll a[N],b[N];
vector<int>s[N];
int fa[N][L],dep[N];
Mat init(int u,ll t){
	Mat c(3,3);
	if(k==1)c.a[0][0]=a[u];
	else if(k==2)c.a[0][1]=0,c.a[0][0]=c.a[1][0]=a[u];
	else{
		c.a[0][0]=c.a[1][0]=c.a[2][0]=a[u];
		c.a[0][1]=c.a[1][2]=0,c.a[1][1]=min(b[u],t);
	}
	return c;
}
void dfs(int u,int f){
	fa[u][0]=f,dep[u]=dep[f]+1,b[u]=1e18;
	for(int v:s[u]){
		if(v==f)continue;
		dfs(v,u),b[u]=min(b[u],a[v]);
	}
	D[u][0]=U[u][0]=init(u,inf);
}
int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	for(int i=L-1;i>=0;i--)
		if((dep[u]-dep[v])>>i)u=fa[u][i];
	if(u==v)return u;
	for(int i=L-1;i>=0;i--)
		if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
Mat QD(int u,int j){
	Mat ret(3,3);ret.EMat();
	for(int i=L-1;i>=0;i--)
		if((j>>i)&1)ret=D[u][i]*ret,u=fa[u][i];
	return ret;
}
Mat QU(int u,int j){
	Mat ret(3,3);ret.EMat();
	for(int i=L-1;i>=0;i--)
		if((j>>i)&1)ret=ret*U[u][i],u=fa[u][i];
	return ret;
}
ll query(int u,int v){
	int Lca=lca(u,v);
	Mat ans=f0*QU(u,dep[u]-dep[Lca])*init(Lca,a[fa[Lca][0]])*QD(v,dep[v]-dep[Lca]);
	return min(ans.a[0][0]-a[v],min(ans.a[0][1],ans.a[0][2]))+a[v];
}
int main(){
	n=read(),m=read(),k=read();
	f0.a[0][k-1]=0,a[0]=inf;
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		s[u].push_back(v),s[v].push_back(u);
	}
	dfs(1,0);D[0][0]=U[0][0]=Mat(3,3);
	for(int j=1;j<L;j++){
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
		for(int i=1;i<=n;i++)
			D[i][j]=D[fa[i][j-1]][j-1]*D[i][j-1];
		for(int i=1;i<=n;i++)
			U[i][j]=U[i][j-1]*U[fa[i][j-1]][j-1];
	}
	for(int u,v;m;m--){
		u=read(),v=read();
		printf("%lld\n",query(u,v));
	}
	
	return 0;
}
posted @ 2023-08-06 20:02  SError  阅读(8)  评论(0编辑  收藏  举报