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

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

裸的树上最大独立集怎么做?设 fx,0 表示在 x 子树中, x 不选的最大答案;fx,1 表示在 x 子树中,x 选的最大答案。则有

fx,0=ysonxmax(fy,0,fy,1)

fx,1=ax+ysonxfy,0

现在要支持修改,怎么办?

发现,每次修改,DP值受到影响的位置是从修改位置到根的一条路径。听上去像是树剖应用的场景?

于是我们考虑树剖,并设 gx,0/1 表示忽略 x 的重儿子时的答案。则,fx,0=max(fy,0,fy,1)+gx,0,fx,1=fy,0+gx,1,其中 yx 的重儿子。

现在,我们考虑将其转成矩阵的形式。则有

[fy,0fy,1][gx,0gx,1gx,0]=[fx,0fx,1]

其中,矩阵乘法的定义是 uv=wwi,j=maxk{ui,k+vk,j}。手推可得其满足结合律。

于是,我们考虑从一个叶子的 f 数组开始(令人惊喜的是,叶子节点的 fg 是相等的(因为其不存在儿子),这使得转移很方便),一路往上乘矩阵,即可得到该叶子所在重链上任意节点的 f 数组。并且,因为任意重链的结尾节点都是叶子,所以通过上述方法可以求出任意节点的 f 数组。

现在考虑带修。发现,一次修改只会影响到根的路径上所有重链顶的父亲的 g 数组,而这样的父亲最多只有 O(logn) 个,故直接暴力跳链修改父亲即可。

时间复杂度 O(nlog2n)

代码:

#include<bits/stdc++.h>
using namespace std;
const int inf=0xd0d0d0d0;
int n,m,a[100100],f[100100][2],g[100100][2];//0:self unchosen 1:self chosen
int fa[100100],son[100100],sz[100100],dfn[100100],rev[100100],top[100100],bot[100100],tot;
vector<int>v[100100];
void dfs1(int x){
	sz[x]=1,f[x][0]=0,f[x][1]=a[x];
	for(auto y:v[x])if(y!=fa[x]){
		fa[y]=x,dfs1(y),sz[x]+=sz[y];
		if(sz[son[x]]<sz[y])son[x]=y;
		f[x][0]+=max(f[y][0],f[y][1]),f[x][1]+=f[y][0];
	}
}
void dfs2(int x){
	dfn[x]=++tot,rev[tot]=x;if(!top[x])top[x]=x;if(son[x])top[son[x]]=top[x],dfs2(son[x]);else bot[top[x]]=x;
	for(auto y:v[x])if(y!=fa[x]&&y!=son[x])dfs2(y),g[x][0]+=max(f[y][0],f[y][1]),g[x][1]+=f[y][0];
}
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
struct Matrix{
	int t[2][2];
	Matrix(){memset(t,inf,sizeof(t));}
	Matrix(int x){t[1][0]=t[0][1]=inf,t[0][0]=t[1][1]=0;}
	int*operator[](const int&x){return t[x];}
	friend Matrix operator*(Matrix u,Matrix v){Matrix w;for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++)w[i][j]=max(w[i][j],u[i][k]+v[k][j]);return w;}
	void print()const{for(int i=0;i<2;i++,puts(""))for(int j=0;j<2;j++)printf("%d ",t[i][j]);}
	int zero(){return max(max(t[0][0],t[1][0]),max(t[0][1],t[1][1]));}
	int one(){return max(t[0][0],t[1][0]);}
}seg[400100];
Matrix Generate(int x){Matrix M;M[0][0]=g[x][0],M[1][0]=g[x][0],M[0][1]=g[x][1],M[1][1]=inf;return M;}
void build(int x,int l,int r){if(l==r)seg[x]=Generate(rev[l]);else build(lson,l,mid),build(rson,mid+1,r),seg[x]=seg[rson]*seg[lson];}
Matrix query(int x,int l,int r,int L,int R){if(l>R||r<L)return Matrix(0);if(L<=l&&r<=R)return seg[x];return query(rson,mid+1,r,L,R)*query(lson,l,mid,L,R);}
void reset(int x,int l,int r,int P){if(l>P||r<P)return;if(l==r)seg[x]=Generate(rev[P]);else reset(lson,l,mid,P),reset(rson,mid+1,r,P),seg[x]=seg[rson]*seg[lson];}
int modify(int x,int y){
	Matrix M;
	for(int i=x;top[i]!=1;)M=query(1,1,n,dfn[top[i]],dfn[bot[top[i]]]),i=fa[top[i]],g[i][0]-=M.zero(),g[i][1]-=M.one();
	g[x][1]+=y-a[x],a[x]=y,reset(1,1,n,dfn[x]);
	for(int i=x;top[i]!=1;)M=query(1,1,n,dfn[top[i]],dfn[bot[top[i]]]),i=fa[top[i]],g[i][0]+=M.zero(),g[i][1]+=M.one(),reset(1,1,n,dfn[i]);
	M=query(1,1,n,1,dfn[bot[1]]);
	return M.zero();
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),g[i][1]=a[i];
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	dfs1(1),dfs2(1),build(1,1,n);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),printf("%d\n",modify(x,y));
	return 0;
}

posted @   Troverld  阅读(52)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示