LG4719 【模板】动态dp

题意

题目描述

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

有\(m\)次操作,每次操作给定\(x,y\),表示修改点\(x\)的权值为\(y\)。

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

输入输出格式

输入格式:

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

第二行,\(V_1,V_2,...,V_n\),代表\(n\)个点的权值。

接下来\(n-1\)行,\(x,y\),描述这棵树的\(n-1\)条边。

接下来\(m\)行,\(x,y\),修改点\(x\)的权值为\(y\)。

输出格式:

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

保证答案在int范围内

输入输出样例

输入样例#1:

10 10
-11 80 -99 -76 56 38 92 -51 -34 47
2 1
3 1
4 3
5 2
6 2
7 1
8 2
9 4
10 7
9 -44
2 -17
2 98
7 -58
8 48
3 99
8 -61
9 76
9 14
10 93

输出样例#1:

186
186
190
145
189
288
244
320
258
304

说明

对于30%的数据,\(1\le n,m\le 10\)
对于60%的数据,\(1\le n,m\le 1000\)
对于100%的数据,\(1\le n,m\le 10^5\)

分析

参照胡小兔的题解。

猫锟在WC2018讲的黑科技——动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样。(这道题也是PPT中的例题)

动态DP的一个套路是把DP转移方程写成矩阵乘法,然后用线段树(树上的话就是树剖)维护矩阵,这样就可以做到修改了。

注意这个“矩阵乘法”不一定是我们常见的那种乘法和加法组成的矩阵乘法。设\(A?B=C\),常见的那种矩阵乘法是这样的:
\[
C_{i,j} = \sum_{k=1}^n A_{i,k}*B_{k,j}
\]
而这道题中的矩阵乘法是这样的:
\[
C_{i,j} = \max_{k=1}^n A_{i,k} + B_{k,j}
\]
这就相当于常见矩阵乘法中的加法变成了max,乘法变成了加法。类似于乘法和加法的五种运算律,这两种变化也满足“加法交换律”、“加法结合律”、“max交换律”、“max结合律”和“加法分配律“。那么这种矩阵乘法显然也满足矩阵乘法结合律,就像正常的矩阵乘法一样,可以用线段树维护。

接下来我们来构造矩阵。首先研究DP方程。

就像“没有上司的舞会”一样,\(f[i][0]\)表示子树\(i\)中不选\(i\)的最大权独立集大小,\(f[i][1]\)表示子树\(i\)中选\(i\)的最大权独立集大小。

但这是动态DP,我们需要加入动态维护的东西以支持修改操作。考虑树链剖分。假设我们已经完成了树链剖分,剖出来的某条重链看起来就像这样,右边的是在树上深度较大的点:

此时,比这条重链的top深度大且不在这条重链上的点的DP值都是已经求出来的(这可以做到)。我们把它们的贡献,都统一于它们在这条重链上对应的那个祖先上。

具体来说,设\(g[i][0]\)表示不选i时,\(i\)不在链上的子孙的最大权独立集大小,\(g[i][1]\)表示选\(i\)时,\(i\)不在链上的子孙再加上\(i\)自己的最大权独立集大小。与一般的DP状态的意义相比,除去了重儿子的贡献,这是为了利用树剖从任意节点到根最多\(\lceil \log_2 n \rceil\)条重链的性质,便于维护以后的修改操作。

假如\(i\)右面的点是\(i+1\), 那么可以得出:
\[
f[i][0]=g[i][0]+\max\{f[i+1][0],f[i+1][1]\} \f[i][1]=g[i][1]+f[i+1][0]
\]
矩阵也就可以构造出来了:
\[
\left(
\begin{matrix}
g[i][0] & g[i][0] \g[i][1] & -\infty
\end{matrix}
\right)
*
\left(
\begin{matrix}
f[i+1][0] \\
f[i+1][1]
\end{matrix}
\right)=
\left(
\begin{matrix}
f[i][0] \\
f[i][1]
\end{matrix}
\right)
\]
读者可以动笔验证一下。(注意我们在这里用的“新矩阵乘法”的规则:原来的乘变成加,加变成取max。)

那么基本思路就很清楚了:树剖,维护区间矩阵乘积,单个矩阵代表\(g\),一条重链的矩阵乘积代表\(f\)。修改的时候,对于被修改节点到根节点路径上的每个重链(由下到上),先单点修改\(g[i][1]\),然后求出这条重链的\(top\)在修改之后的\(f\)值,然后更新\(fa[top]\)的\(g\)值,一直进行下去。

每次答案就是节点1的\(f\)值。

时间复杂度\(O(8n+8m\log^2 n)\)

代码

用bfs实现了dfs1之后,直接用拓扑序实现了dfs2,非常巧妙,常数小。

程序实现的时候把那个\(-\infty\)设成0也没有任何问题……大概是数据弱的缘故。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;
    rg char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
        data=data*10+ch-'0',ch=getchar();
    return data*w;
}
template<class T>il T read(rg T&x){
    return x=read<T>();
}
typedef long long ll;
using namespace std;

co int N=1e5+5;
int n,m,a[N];
int ecnt,adj[N],nxt[2*N],go[2*N];
int fa[N],son[N],sze[N],top[N],idx[N],pos[N],tot,ed[N];
ll f[N][2];
struct matrix{
    ll g[2][2];
    matrix(){
        memset(g,0,sizeof g);
    }
    matrix operator*(co matrix&b)co{
        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]+b.g[k][j]);
        return c;
    }
}val[N],data[4*N];

void add(int u,int v){
    go[++ecnt]=v,nxt[ecnt]=adj[u],adj[u]=ecnt;
}
void init(){
    static int que[N];
    que[1]=1;
    for(int ql=1,qr=1;ql<=qr;++ql)
        for(int u=que[ql],e=adj[u],v;e;e=nxt[e])
            if((v=go[e])!=fa[u])
                fa[v]=u,que[++qr]=v;
    for(int qr=n,u;qr;--qr){
        sze[u=que[qr]]++;
        sze[fa[u]]+=sze[u];
        if(sze[u]>sze[son[fa[u]]]) son[fa[u]]=u;
    }
    for(int ql=1,u;ql<=n;++ql)
        if(!top[u=que[ql]]){
            for(int v=u;v;v=son[v])
                top[v]=u,idx[pos[v]=++tot]=v;
            ed[u]=tot;
        }
    for(int qr=n,u;qr;--qr){
        u=que[qr];
        f[u][1]=max(0,a[u]);
        for(int e=adj[u],v;e;e=nxt[e])
            if(v=go[e],v!=fa[u]){
                f[u][0]+=max(f[v][0],f[v][1]);
                f[u][1]+=f[v][0];
            }
    }
}

void build(int k,int l,int r){
    if(l==r){
        ll g0=0,g1=a[idx[l]];
        for(int u=idx[l],e=adj[u],v;e;e=nxt[e])
            if((v=go[e])!=fa[u]&&v!=son[u])
                g0+=max(f[v][0],f[v][1]),g1+=f[v][0];
        data[k].g[0][0]=data[k].g[0][1]=g0;
        data[k].g[1][0]=g1;
        val[l]=data[k];
        return;
    }
    int mid=l+r>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    data[k]=data[k<<1]*data[k<<1|1];
}
void change(int k,int l,int r,int p){
    if(l==r){
        data[k]=val[l];
        return;
    }
    int mid=l+r>>1;
    if(p<=mid) change(k<<1,l,mid,p);
    else change(k<<1|1,mid+1,r,p);
    data[k]=data[k<<1]*data[k<<1|1];
}
matrix query(int k,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr) return data[k];
    int mid=l+r>>1;
    if(qr<=mid) return query(k<<1,l,mid,ql,qr);
    if(ql>mid) return query(k<<1|1,mid+1,r,ql,qr);
    return query(k<<1,l,mid,ql,qr)*query(k<<1|1,mid+1,r,ql,qr);
}
matrix ask(int u){
    return query(1,1,n,pos[top[u]],ed[top[u]]);
}
void path_change(int u,int x){
    val[pos[u]].g[1][0]+=x-a[u];
    a[u]=x;
    matrix od,nw;
    while(u){
        od=ask(top[u]);
        change(1,1,n,pos[u]);
        nw=ask(top[u]);
        u=fa[top[u]];
        val[pos[u]].g[0][0]+=max(nw.g[0][0],nw.g[1][0])-max(od.g[0][0],od.g[1][0]);
        val[pos[u]].g[0][1]=val[pos[u]].g[0][0];
        val[pos[u]].g[1][0]+=nw.g[0][0]-od.g[0][0];
    }
}
int main(){
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    read(n),read(m);
    for(int i=1;i<=n;++i) read(a[i]);
    for(int i=1,u,v;i<n;++i)
        read(u),read(v),add(u,v),add(v,u);
    init();
    build(1,1,n);
    int u,x;
    matrix t;
    while(m--){
        read(u),read(x);
        path_change(u,x);
        t=ask(1);
        printf("%lld\n",max(t.g[0][0],t.g[1][0]));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/autoint/p/10430328.html

时间: 2024-07-30 07:37:59

LG4719 【模板】动态dp的相关文章

[LuoguP4719][模板]动态DP(动态DP)

[LuoguP4719][模板]动态DP(动态DP) 题面 给出一棵\(n\)个点的树,点带权.\(m\)组修改,每次修改一个点的点权,并询问整棵树最大权独立集大小. 分析 约定:\(child(x)\)表示\(x\)的儿子集合,\(son(x)\)表示\(x\)的重儿子. 先写出树形DP.设\(f_{x,0/1}\)表示不选或选\(x\),\(x\)的子树里最大权独立集的大小. 如果不选\(x\),那么儿子\(y\)可以任意选 \[f_{x,0}=\sum_{y \in child(x)} \

std中vector的实现原理(标准模板动态库中矢量的实现原理)

我们实现的数据结构是为了解决在运行过程中动态的开辟空间使用(例如我们不停的输入,输入的多少我们不确定) 原理两种: 一.笨办法 我们第一次用多少空间,开辟多少空间A 我们第二次用空间,会开辟大于第一次开辟的空间B,将A里的数据拷贝到B中,然后释放A,在C中写入新的数据 缺点:在于拷贝的次数太多,效率低 二.改进的办法 矢量有一个参数,里面填写预留的空间,加入我们填写的预留空间大小是B,这里是预留,并没有真正的开辟物理内存,预留的作用于如果这时候如果需要开辟空间做其他事情,开辟的空间会避开B,这样

4712: 洪水 基于链分治的动态DP

国际惯例的题面:看起来很神的样子......如果我说这是动态DP的板子题你敢信?基于链分治的动态DP?说人话,就是树链剖分线段树维护DP.既然是DP,那就先得有转移方程.我们令f[i]表示让i子树中的叶子节点全部与根不联通,所需要的最小代价,v[i]为输入的点权.显然f[i]=min(v[i],sigma(f[soni])),边界条件是,如果i是叶子节点,则f[i]=v[i].我们需要用链分治去维护这个DP,所以要把DP拆成重链和轻链独立的形式.我们还是用f[i]表示让i子树中的叶子节点全部与根

[动态dp]线段树维护转移矩阵

背景:czy上课讲了新知识,从未见到过,总结一下. 所谓动态dp,是在动态规划的基础上,需要维护一些修改操作的算法. 这类题目分为如下三个步骤:(都是对于常系数齐次递推问题) 1先不考虑修改,不考虑区间,直接列出整个区间的dp方程.这个是基础,动态dp无论如何还是dp(这一步是一般是重点) 2.列出转移矩阵.由于有很多修改操作,我们将数据集中在一起处理,还可以利用矩阵结合律,并且区间比较好提取,(找一段矩阵就好了),修改也方便. 3.线段树维护矩阵.对于修改,我们就是在矩阵上进行修改,对于不同的

UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】

题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一部分将代价加在整条链上,用$d$数组保存. 这时候我们可以发现,一条从$u$到$v$的路径的代价相当于是$d[LCA(u,v)]+\sum_{x \in edge(u,v)}g[x]$. 如果是静态的,可以用树形DP解决. 看过<神奇的子图>的同学都知道,叶子结点是从它的儿子中取两个最大的出来,所

bzoj 4712 洪水——动态DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4712 因为作为动态DP练习而找到,所以就用动态DP做了,也没管那种二分的方法. 感觉理解似乎加深了. 果然初始权值也都是非负的. 所以 dp[cr] 表示当前子树与自己的叶子都断开了的最小代价,则 dp[cr]=min{ sigma dp[v] , w[cr] }(v是cr的直接孩子). 但这样的话,修改的时候需要把自己到根的路径都走一遍.不过查询是O(1)的,所以考虑分配一下. 走到根的

uoj#268. 【清华集训2016】数据交互(动态dp+堆)

传送门 动态dp我好像还真没咋做过--通过一个上午的努力光荣的获得了所有AC的人里面的倒数rk3 首先有一个我一点也不觉得显然的定理,如果两条路径相交,那么一定有一条路径的\(LCA\)在另一条路径上 于是我们可以对于每一个点记录两个值,一个\(a_i\)表示\(LCA\)在\(i\)点的所有路径的权值之和,一个是\(b_i\),表示经过点\(i\)且\(LCA\)不在点\(i\)的所有路径的权值之和 那么对于一条路径\((u,v)\),它的权值就是\(b_{LCA(u,v)}+\sum_{i\

回文串 --- 动态dp UVA 11584

题目链接: https://cn.vjudge.net/problem/34398/origin 本题的大意其实很简单,就是找回文串,大致的思路如下: 1. 确定一个回文串,这里用到了自定义的check函数原理如下: 传入le, ri两个值(定义从1开始), s+1 = aaadbccb. a a a d b c c b 1 2 3 4 5 6 7 8 比如,le = 5, ri = 8. 则s[5] == s[8]成立 le++ ri-- 再比较 s[6] == s[7]? 成立 le++,

SPOJ GSS3 (动态dp)

题意 题目链接 Sol 这题可以动态dp做. 设\(f[i]\)表示以\(i\)为结尾的最大子段和,\(g[i]\)表示\(1-i\)的最大子段和 那么 \(f[i] = max(f[i - 1] + a[i], a[i])\) \(g[i] = max(g[i - 1], f[i])\) 发现只跟前一项有关,而且\(g[i]从\)f[i]$转移过来的那一项可以直接拆开 那么构造矩阵 \[ \begin{bmatrix} a_{i} & -\infty & \dots a_{i} \\ a