• luoguP4719 【模板】动态 DP


    题目描述

    给定一棵n个点的树,点带点权。

    m次操作,每次操作给定x,y,表示修改点x的权值为y

    你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

    输入输出格式

    输入格式:

    第一行,n,m分别代表点数和操作数。

    第二行,V1,V2,...,Vn,代表n个点的权值。

    接下来n−1行,x,y,描述这棵树的n−1条边。

    接下来m行,x,y,修改点x的权值为y

    输出格式:

    对于每个操作输出一行一个整数,代表这次操作后的树上最大权独立集。

    保证答案在int范围内

    动态DP讲解:
     
    考虑最大独立集转移方程:

    $F[u][0]$ 表示 $u$ 点为根,不选$u$点的最大独立集.

    $F[u][1]$ 表示 $u$ 点为根,选$u$点的最大独立集.

    转移:

    $F[u][0]=sum_{v in son[u]}max(F[u][0],F[u][1])$

    $F[u][1]=sum_{v in son[u]}F[u][0]$

    不难发现,每当我们修改一个点的点权,我们只会影响到该点到根节点这条链上的DP值.

    如果树的高度是 $log(n)$,那么暴力跳链进行修改是完全可行的.

    然而,题中树的高度可能会被恶意卡成 $O(n)$ 级别的.

    那么,暴力跳链的总体复杂度就退化为 $O(nm)$ 了.

    我们需要一个优秀的做法,使得每次修改都不受树高影响.

    方法一 :  树链剖分 + 线段树 + 矩阵乘法.

    注意的是,我们这里的矩阵乘法是长这样的:

     
    Matrix operator*(Matrix a,Matrix b)
    {
        Matrix c; 
        c[0][0]=max(a[0][0]+b[0][0],a[0][1]+b[1][0]); 
        c[0][1]=max(a[0][0]+b[0][1],a[0][1]+b[1][1]); 
        c[1][0]=max(a[1][0]+b[0][0],a[1][1]+b[1][0]); 
        c[1][1]=max(a[1][0]+b[0][1],a[1][1]+b[1][1]);      
        return c; 
    }
    

    我们用矩阵乘法维护每一次链上的转移.

    我们用矩阵乘法维护每一次链上的转移.

    根据常识,矩阵乘法要求满足结合律,而每一次状态转移也是满足结合律的.

    对于每一个点开一个矩阵 $M_{u}$.

     $M_{u}=egin{bmatrix} F_{u,0} & F_{u,0}\ F_{u,1}  & -infty end{bmatrix}$

     考虑已经得到一个点 $i$ 的 $F_{i,0}$ 及 $F_{i,1}$,以及 $fa_{i}$ 的 $M_{fa_{i}}$.
     
     我们就可以通过矩阵乘法转移到 $fa_{i}$ 的方程了 !

     设 $u=fa_{i}$

     即 $egin{bmatrix} F_{u,0} & F_{u,0}\ F_{u,1}  & -infty end{bmatrix} imesegin{bmatrix} F_{i,0}\F_{i,1} end{bmatrix}=egin{bmatrix} F_{u,0}\F_{u,1} end{bmatrix}$

     是不是十分巧妙 ? (注:矩阵中的 $F[u][0]$ 与 $F[u][1]$ 指的是不包括该点重儿子的DP值,$i$ 是该点的重儿子,我们后文会提及)

     优秀的是,这些矩阵转移是由线段树来维护的.

     准确来说,重链上的线段树.

     我们引入树链剖分.
      
     
     
     
        
     
     
     
     考虑树剖的原理:

    树剖将树剖成不超过 $log(n)$ 个重链,而树上任意两点最终都会蹦到同一个重链上.

     最坏也只会跳 $log(n)$ 次链顶,所以时间复杂度是十分优秀的. 

     而在动态 DP 中,我们也是用蹦链的方式来实现.

     即每条重链用一颗线段树维护,线段树来维护一段区间的矩阵乘法.

     这样,每次修改与查询就在对应的线段树中进行.
     
    考虑修改:

    在上文中,我们已经知道,每次修改只会影响到该点到根节点的一条路径,我们只需修改该点到根节点这条链上的信息即可.

    现在在一条重链上的线段树进行单点修改,这是很简单的.

    然后,我们再跳到该点所属重链的父亲上(设为 $fa$)

    由于修改的是轻儿子,所以 $fa$ 的 $F[u][0]$ 与 $F[u][1]$ 都需要改变.
     
    考虑查询:

    由于我们在修改时已经将所有信息全部修改完毕,只需调用出根节点所在的重链的线段树, 并查一下区间矩阵乘法的结果即可.
     

    Code: 

    #include<bits/stdc++.h> 
    #define setIO(s) freopen(s".in","r",stdin) 
    #define maxn 300000 
    #define lson (now<<1) 
    #define rson ((now<<1)|1) 
    #define ll long long 
    const ll inf = 1e17; 
    using namespace std;
    int hd[maxn],to[maxn],nex[maxn],V[maxn],hson[maxn],fa[maxn],dfn[maxn], ln[maxn],F[maxn][2],siz[maxn],top[maxn],bot[maxn]; 
    int edges,tim,n,Q; 
    void add(int u,int v)
    {
    	nex[++edges]=hd[u],hd[u]=edges,to[edges]=v; 
    }
    void dfs1(int u,int ff)
    {
    	siz[u]=1,fa[u]=ff; 
    	for(int i=hd[u];i;i=nex[i])
    	{
    		if(to[i]==ff) continue; 
    		dfs1(to[i],u); 
    		siz[u]+=siz[to[i]]; 
    		if(siz[to[i]] > siz[hson[u]]) hson[u]=to[i]; 
    	}
    }
    void dfs2(int u,int tp)
    {
    	top[u]=tp,ln[++tim]=u,dfn[u]=tim; 
    	if(hson[u]) 
    		dfs2(hson[u], tp), bot[u]=bot[hson[u]]; 
    	else 
    		bot[u]=u; 
    	for(int i=hd[u];i;i=nex[i])
    	{
    		if(to[i]==fa[u]||to[i]==hson[u]) continue; 
    		dfs2(to[i],to[i]);  
    	}
    }
    void dfs(int u)
    {
    	F[u][0]=0,F[u][1]=V[u]; 
    	for(int i=hd[u];i;i=nex[i])
    	{
    		if(to[i]==fa[u]) continue; 
    		dfs(to[i]); 
    		F[u][0]+=max(F[to[i]][1],F[to[i]][0]); 
    		F[u][1]+=F[to[i]][0]; 
    	}
    }
    struct Matrix
    {
    	ll a[2][2]; 
    	ll*operator[](int x){ return a[x]; }
    }t[maxn<<1],tmp[maxn<<1]; 
    Matrix operator*(Matrix a,Matrix b)
    {
    	Matrix c; 
    	c[0][0]=max(a[0][0]+b[0][0],a[0][1]+b[1][0]); 
    	c[0][1]=max(a[0][0]+b[0][1],a[0][1]+b[1][1]); 
    	c[1][0]=max(a[1][0]+b[0][0],a[1][1]+b[1][0]); 
    	c[1][1]=max(a[1][0]+b[0][1],a[1][1]+b[1][1]);      
    	return c; 
    }
    void Build(int now,int l,int r)
    {
    	if(l==r)
    	{
    		int u=ln[l];  
    		ll g0=0,g1=V[u]; 
    		for(int i=hd[u];i;i=nex[i])
    		{
    			int v=to[i];
    			if(v==fa[u]||v==hson[u]) continue; 
    			g0+=max(F[v][0],F[v][1]); 
    			g1+=F[v][0]; 
    		}
    		t[now]=tmp[l]=(Matrix) {g0,g0,g1,-inf}; 
    		return; 
    	}
    	int mid=(l+r)>>1; 
    	Build(lson,l,mid),Build(rson,mid+1,r); 
    	t[now]=t[lson]*t[rson]; 
    }
    void Modify(int now,int l,int r,int p)
    {
    	if(l==r) 
    	{
    	    t[now]=tmp[l]; 
    	    return;  
    	}
    	int mid=(l+r)>>1; 
    	if(p<=mid) Modify(lson,l,mid,p); 
    	else Modify(rson,mid+1,r,p); 
    	t[now]=t[lson]*t[rson]; 
    }
    Matrix Query(int now,int l,int r,int L,int R)
    {
    	if(L==l&&r==R) return t[now];
    	int mid=(l+r)>>1; 
    	if(R<=mid) return Query(lson,l,mid,L,R); 
    	if(L>mid) return Query(rson,mid+1,r,L,R); 
    	return Query(lson,l,mid,L,mid)*Query(rson,mid+1,r,mid+1,R); 
    }
    void Update(int u,int w)
    {
    	tmp[dfn[u]][1][0]+=w-V[u],V[u]=w; 
    	while(u)
    	{
    		Matrix a=Query(1,1,n,dfn[top[u]],dfn[bot[u]]); 
    		Modify(1,1,n,dfn[u]); 
    		Matrix b=Query(1,1,n,dfn[top[u]],dfn[bot[u]]); 
    		u=fa[top[u]];
    		if(!u) break; 
    		int x = dfn[u]; 
    		ll g0=a[0][0],g1=a[1][0],f0=b[0][0],f1=b[1][0]; 
    		tmp[x][0][0]=tmp[x][0][1]=tmp[x][0][0]+max(f0,f1)-max(g0,g1); 
    		tmp[x][1][0]=tmp[x][1][0]+f0-g0; 
    	}
    }
    int main()
    {
    	// setIO("input"); 
    	scanf("%d%d",&n,&Q); 
    	for(int i=1;i<=n;++i) scanf("%d",&V[i]); 
    	for(int i=1,u,v;i<n;++i) 
    	{
    		scanf("%d%d",&u,&v),add(u,v),add(v,u); 
    	}
    	dfs1(1,0),dfs2(1,1),dfs(1),Build(1,1,n); 
    	while(Q--)
    	{
    		int x,w; 
    		scanf("%d%d",&x,&w);
    		Update(x,w); 
    		Matrix ans=Query(1,1,n,dfn[1],dfn[bot[1]]);                
    		printf("%lld
    ",max(ans[0][0],ans[1][0])); 
    	}
    	return 0; 
    }
    

      

  • 相关阅读:
    docker基础:docker网络模式
    WEB架构师成长之路之一-走正确的路(转载)
    DDD(领域驱动设计)
    C#泛型和泛型约束(转载)
    MES系统介绍
    vue中 computed和watch的一些简单理解(区别)(转载)
    sqlserver常用表值函数
    SQLServerAgent 当前未运行,因此无法将此操作通知它。
    浅谈敏捷开发(转载)
    认证、授权、鉴权和权限控制(转载)
  • 原文地址:https://www.cnblogs.com/guangheli/p/10965885.html
Copyright © 2020-2023  润新知