qwq大概是混乱的一个题。

首先,还是从一个比较基础的想法开始想起。

如果每次暴力修改的话,那么每次就可以暴力树形dp

令\(dp[x][0/1]\)表示\(x\)的子树中,是否选择\(x\)这个点的最大权独立集。

如果这个点不选,那么他的所有儿子都是可以选的。

如果这个点选的,那么只能加上他的所有儿子不选的收益。

因为收益可能存在负数,所以要特别处理一下

void dfs(int x,int fa)
{
f[x][0]=0;
f[x][1]=val[x];
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
dfs(p,x);
f[x][0]=max(f[x][0],f[x][0]+max(f[p][1],f[p][0]));
f[x][1]=max(f[x][1],f[x][1]+f[p][0]);
}
}

那么暴力修改的版本就迎刃而解了。

但是如何做正常的修改呢?

这时候就需要一个黑科技了

动态dp

动态dp大致上的思路就是通过矩阵来实现dp的转移,从而能做到快速修改的效果。

那么回到这个题。

由于是树上,所以不难相当用树链剖分+线段树来求解问题

由于刚才那个\(dp\)状态不太好优化,我们不妨来定义一下新的状态。

我们令\(dp[i][0/1]\)表示\(i\)的子树内,不考虑\(i\)的重儿子的是否选\(i\)的最大独立集。

然后用\(dp1\)表示上文的\(dp\)状态。

那么通过两遍\(dfs\)确定轻重儿子和相关信息之后,其实两个数组也就比较好处理了。

void solve(int x,int fa) //这里dp数组是忽略重儿子的贡献,dp1则是原来的dp,不能开成一个数组的原因是因为那样就不能保证一个点只会有重儿子被忽略
{
dp[x][0]=0;
dp[x][1]=val[x];
dp1[x][0]=0;
dp1[x][1]=val[x];
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
solve(p,x);
dp1[x][0]=max(dp1[x][0],dp1[x][0]+max(dp1[p][0],dp1[p][1]));
dp1[x][1]=max(dp1[x][1],dp1[x][1]+dp1[p][0]);
if (p!=son[x])
{
dp[x][0]=max(dp[x][0],dp[x][0]+max(dp1[p][0],dp1[p][1]));
dp[x][1]=max(dp[x][1],dp[x][1]+dp1[p][0]);
}
}
}

需要注意的是,我们的\(dp\)数组,只是忽略当前子树的重儿子。qwq所以处理的时候,要用\(dp1\)更新\(dp\)。

但是其实到现在,对这个题还是没什么进展。

现在考虑怎么用这个\(dp\)数组来算答案呢。

我们令\(f\)表示现在的\(dp\),然后用\(g\)表示最终的\(ans\)。

那么不难发现$$g[i][0]=f[i][0]+max(g[son[i]][0/1])$$

\[g[i][1]=f[i][1]+g[son[i]][0]
\]

如果我们定义一种新的矩阵运算\(max\),表示\(c[i][j]=max(c[i][j],a[i][k]+b[k][j])\)

那么实际上我们可以通过一个矩阵来完成转移?

没错

观察不难发现,对于每一个点的转移矩阵,其实就是那个\(f\)的矩阵,因为这个是可以实现就预处理好的。

那么由于对于一条重链来说,链尾的元素的\(g\)与\(f\)是相等的,并且矩阵乘法具有结合律,所以我们可以直接通过预处理某一个点,到他所在重链的链尾的区间矩阵乘积的和来求出这个点的\(g\),实际上每次询问答案,只需要查询1的值即可。

那么现在的问题就剩下怎么修改了

其实上面的转移过程,主要是为了修改做准备。

首先修改的时候,会修改这个单点的矩阵的值。

然后依次会修改每一条重链的链头\(fa\)的转移矩阵(相当于每一条重链的链头会发生变化,而他相对于他的父亲又是轻儿子,所以会影响)

这里修改的时候,我们选择一个策略就是用他原来的贡献,减去他现在的贡献。

所以要记录一个\(pre\)数组表示这个点的转移矩阵是什么,然后每次计算起来就比较方便,总之还是细节比较多的。

qwqqqqq

直接看代码吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#include<set>
#define pb push_back
#define mk make_pair
#define ll long long
#define lson ch[x][0]
#define rson ch[x][1]
#define int long long using namespace std; inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
} const int maxn = 1e6+1e2;
const int maxm = 2*maxn;
const int inf = -1e9; struct Ju{
int x,y;
int a[3][3];
Ju operator * (Ju b)
{
Ju ans;
ans.x=x;
ans.y=b.y;
for (int i=1;i<=ans.x;i++)
for (int j=1;j<=ans.y;j++)
ans.a[i][j]=inf;
for (int i=1;i<=ans.x;i++)
for (int j=1;j<=ans.y;j++)
for (int k=1;k<=y;k++)
ans.a[i][j]=max(ans.a[i][j],a[i][k]+b.a[k][j]);
return ans;
}
}; void print(Ju x)
{
cout<<x.x<<" "<<x.y<<endl;
for (int i=1;i<=2;i++)
{
for (int j=1;j<=2;j++) cout<<x.a[i][j]<<" ";
cout<<endl;
}
cout<<"----------------"<<endl;
} int point[maxn],nxt[maxm],to[maxm];
Ju f[4*maxn];
int dp[maxn][2];
int cnt,n,m,newnum[maxn];
Ju pre[maxn]; //pre表示之前的矩阵是多少,这个便于计算贡献。
int dfn[maxn],top[maxn],fa[maxn],tail[maxn];
int son[maxn],size[maxn];
int tot,q,val[maxn];
int back[maxn];
int dp1[maxn][2]; void addedge(int x,int y)
{
nxt[++cnt]=point[x];
to[cnt]=y;
point[x]=cnt;
}
//因为树上合并是从下到上,所以线段树的合并应该是右儿子乘左儿子。 void up(int root)
{
f[root]=f[2*root+1]*f[2*root];
} void build(int root,int l,int r)
{
if (l==r)
{
f[root].x=2;
f[root].y=2;
f[root].a[1][1]=dp[back[l]][0];
f[root].a[1][2]=dp[back[l]][1];
f[root].a[2][1]=dp[back[l]][0];
f[root].a[2][2]=inf;
//pre[back[l]]=f[root];
// cout<<back[l]<<" "<<l<<" ";
// print(f[root]);
return;
}
int mid = l+r >> 1;
build(2*root,l,mid);
build(2*root+1,mid+1,r);
up(root);
} void update(int root,int l,int r,int x,Ju p)
{
if (l==r)
{
f[root]=p;
return;
}
int mid = l+r >> 1;
if (x<=mid) update(2*root,l,mid,x,p);
if (x>mid) update(2*root+1,mid+1,r,x,p);
up(root);
} Ju query(int root,int l,int r,int x,int y)
{
//cout<<l<<" "<<r<<" "<<x<<" "<<y<<endl;
if (x<=l && r<=y)
{
return f[root];
}
int mid = l+r >> 1;
if (y<=mid) return query(2*root,l,mid,x,y);
if (x>mid) return query(2*root+1,mid+1,r,x,y);
return query(2*root+1,mid+1,r,x,y)*query(2*root,l,mid,x,y);
} void dfs1(int x,int faa)
{
size[x]=1;
int maxson=-1;
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==faa) continue;
dfs1(p,x);
fa[p]=x;
size[x]+=size[p];
if (size[p]>maxson)
{
maxson=size[p];
son[x]=p;
}
}
} void dfs2(int x,int chain)
{
tail[chain]=x;
top[x]=chain;
newnum[x]=++tot;
back[tot]=x;
if (!son[x]) return;
dfs2(son[x],chain);
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (!newnum[p]) dfs2(p,p);
}
} void solve(int x,int fa) //这里dp数组是忽略重儿子的贡献,dp1则是原来的dp,不能开成一个数组的原因是因为那样就不能保证一个点只会有重儿子被忽略
{
dp[x][0]=0;
dp[x][1]=val[x];
dp1[x][0]=0;
dp1[x][1]=val[x];
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
solve(p,x);
dp1[x][0]=max(dp1[x][0],dp1[x][0]+max(dp1[p][0],dp1[p][1]));
dp1[x][1]=max(dp1[x][1],dp1[x][1]+dp1[p][0]);
if (p!=son[x])
{
dp[x][0]=max(dp[x][0],dp[x][0]+max(dp1[p][0],dp1[p][1]));
dp[x][1]=max(dp[x][1],dp[x][1]+dp1[p][0]);
}
}
} void modify(int x,int y)//本质上,一次修改,就是 先单点修改, 然后依次修改每一条重链的链头的父亲(因为同一条链是可以直接乘法得到的,所以不用修改)
{
Ju tmp = query(1,1,n,newnum[x],newnum[x]);
//print(tmp);
tmp.a[1][2]+=y-val[x];
val[x]=y;
//print(tmp);
update(1,1,n,newnum[x],tmp);
for (int now=top[x];now!=1;now=top[now])
{
int faa = fa[now];
Ju ymh = query(1,1,n,newnum[faa],newnum[faa]); //当前点的父亲修改之前的值
Ju lyf = query(1,1,n,newnum[now],newnum[tail[top[now]]]); //这一段重链的值
ymh.a[1][1]+=max(lyf.a[1][1],lyf.a[1][2])-max(pre[now].a[1][1],pre[now].a[1][2]);
ymh.a[1][2]+=lyf.a[1][1]-pre[now].a[1][1];
ymh.a[2][1]=ymh.a[1][1];
update(1,1,n,newnum[faa],ymh);
pre[now]=lyf;//每次更新完他的父亲,就把pre数组修改
now=fa[now];
//cout<<now<<endl;
//print(ymh);
}
} signed main()
{
n=read(),q=read();
for (int i=1;i<=n;i++) val[i]=read();
for (int i=1;i<n;i++)
{
int x=read(),y=read();
addedge(x,y);
addedge(y,x);
}
dfs1(1,0);
dfs2(1,1);
solve(1,0);
// for (int i=1;i<=n;i++)
// {
// cout<<i<<" "<<tail[top[i]]<<" "<<son[i]<<" "<<dp[i][0]<<" "<<dp[i][1]<<endl;
// }
build(1,1,n);
//cout<<query(1,1,n,newnum[1],newnum[tail[top[1]]]).a[1][1]<<endl;
for (int i=1;i<=n;i++)
{
pre[i]=query(1,1,n,newnum[i],newnum[tail[top[i]]]);
//print(pre[i]);
}
//return 0;
for (int i=1;i<=q;i++)
{
int x=read(),y=read();
modify(x,y);
Ju ymh = query(1,1,n,newnum[1],newnum[tail[top[1]]]);
cout<<max(ymh.a[1][1],ymh.a[1][2])<<"\n";
}
return 0;
}

洛谷4719 【模板】动态dp 学习笔记(ddp 动态dp)的更多相关文章

  1. dp学习笔记(各种dp,比较杂)

    HDU1176 中文题意不多解释了. 建一个二维dp数组,dp[ i ][ j ]表示第 i 秒落在 j 处一个馅饼.我们需要倒着DP,为什么呢,从 0秒,x=5处出发,假如沿数组正着往下走,终点到哪 ...

  2. 树形DP 学习笔记(树形DP、树的直径、树的重心)

    前言:寒假讲过树形DP,这次再复习一下. -------------- 基本的树形DP 实现形式 树形DP的主要实现形式是$dfs$.这是因为树的特殊结构决定的——只有确定了儿子,才能决定父亲.划分阶 ...

  3. 数位DP学习笔记

    数位DP学习笔记 什么是数位DP? 数位DP比较经典的题目是在数字Li和Ri之间求有多少个满足X性质的数,显然对于所有的题目都可以这样得到一些暴力的分数 我们称之为朴素算法: for(int i=l_ ...

  4. 树形DP 学习笔记

    树形DP学习笔记 ps: 本文内容与蓝书一致 树的重心 概念: 一颗树中的一个节点其最大子树的节点树最小 解法:对与每个节点求他儿子的\(size\) ,上方子树的节点个数为\(n-size_u\) ...

  5. Web Service学习笔记:动态调用WebService

    原文:Web Service学习笔记:动态调用WebService 多数时候我们通过 "添加 Web 引用..." 创建客户端代理类的方式调用WebService,但在某些情况下我 ...

  6. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  7. DP学习笔记

    DP学习笔记 可是记下来有什么用呢?我又不会 笨蛋你以后就会了 完全背包问题 先理解初始的DP方程: void solve() { for(int i=0;i<;i++) for(int j=0 ...

  8. MyBatis:学习笔记(4)——动态SQL

    MyBatis:学习笔记(4)——动态SQL 如果使用JDBC或者其他框架,很多时候需要你根据需求手动拼装SQL语句,这是一件非常麻烦的事情.MyBatis提供了对SQL语句动态的组装能力,而且他只有 ...

  9. MyBatis:学习笔记(4)——动态SQL

    MyBatis:学习笔记(4)——动态SQL

  10. [C++学习笔记14]动态创建对象(定义静态方法实现在map查找具体类名对应的创建函数,并返回函数指针,map真是一个万能类)good

    [C++学习笔记14]动态创建对象   C#/Java中的反射机制 动态获取类型信息(方法与属性) 动态创建对象 动态调用对象的方法 动态操作对象的属性 前提:需要给每个类添加元数据 动态创建对象 实 ...

随机推荐

  1. 你不知道的echarts,前端鲍哥带你研究!

    前言 相信不少前端小伙伴刚接触 e-charts 肯定有点陌生,但是echarts咱不清楚,charts我们应该很熟悉,没错,echarts 就是我们日常可见的图表,不同的是 echarts 是用代码 ...

  2. springcloud <zuul2.0静态配置>

    server: port: 9006 spring: application: name: cloud-zuul-wangbiao # zipkin: # base-url: http://local ...

  3. mysql switch语句

    SELECT CASE the_order_status WHEN 4 THEN '待收货' WHEN 5 THEN '已收货' ELSE '其他' END AS statuss ,order_id ...

  4. 如何攻击Java Web应用

    越来越多的企业采用Java语言构建企业Web应用程序,基于Java主流的框架和技术及可能存在的风险,成为被关注的重点. 本文从黑盒渗透的角度,总结下Java Web应用所知道的一些可能被利用的入侵点. ...

  5. 如何在MacBook M1上无缝切换Win11和MacOS?

    2020年,MacBook M1发布后,由于其夸张到离谱的性能表现,苹果又一次在知名度和销量上真正实现了双丰收. 抛开M1和MacOS其他的华丽特色不谈,很多习惯了Windows系统的同学,在换了这台 ...

  6. Linux制作Raid

    1.启动服务器 2.在服务器出现提示的时候,根据提示进入Raid管理界面(Ctrl + R),Raid管理界面如下: 3.删除原有的Raid 4.新建Raid 第一步:进入新建Raid的界面 第二步: ...

  7. 痞子衡嵌入式:MCUXpresso IDE下将应用程序RW段分散链接的几种方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是MCUXpresso IDE下将应用程序RW段分散链接的几种方法. 早期的 MCU 芯片,一般都会嵌入内部 Flash 和 RAM,并且 ...

  8. MongoDB(3)- Database 数据库相关

    Database MongoDB 将数据记录存储为文档(特别是 BSON 文档) 这些文档在集合中聚集在一起 数据库存储一个或多个文档集合. 在 MongoDB 里面存在数据库的概念,但没有模式 Sh ...

  9. Intel® QAT加速卡之Ring & Ring Bank

    1. QAT的应用模式 Intel 通讯系列芯片对于每种受支持的加速服务(加密,数据压缩),都支持以下应用模式: 内核模式,其中应用程序和加速服务都在内核中运行空间. 用户空间直接访问在用户空间中运行 ...

  10. Linux目录同步到阿里云OSS工具ossutil

    Linux目录同步到阿里云OSS工具ossutil 背景 ​ 最近公司服务用户激增,常规文件服务器不能满足需求,严重影响性能,决定将静态文件迁移到阿里云OSS,用来解决性能问题,提高用户体验.毕竟之前 ...