热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

没有上司的舞会带修版(ddp)

Solution简化版看了简化版之后容易想到一个dp。$dp_{u,0}$代表$u$的子树内不选$u$的最大答案。$dp_{u,1}$代表$u$的子树内选$u$的最大答案。有转移方

技术分享图片

 


Solution

简化版

看了简化版之后容易想到一个dp。

$dp_{u,0}$代表$u$的子树内不选$u$的最大答案。

$dp_{u,1}$代表$u$的子树内选$u$的最大答案。

有转移方程:

$dp_{u,0}=\sum_{(u,v) \in E} max(dp_{v,0}, dp_{v,1})$。

$dp_{u,1}=(\sum_{(u,v) \in E} dp_{v,0})+a_u$。

然后对于ddp的模板题我们就得到了一个$O(nm)$的优秀算法。

接下来我们就来看看怎么用ddp的套路来切掉这道题。


变成序列dp的形式

因为ddp只是利用树剖把树剖成一条条链,然后再链上做ddp,所以我们要有一个适应链dp的方程和状态。

我们在树剖时只能处理重边的结果,不能处理轻边的结果,所以我们要先把轻边的结果算出来。

设$u$的重儿子为$son(u)$。

因为是在重链上序列dp,所以我们要先把重儿子分离出来。

令$g_{u,0}=\sum_{v \ne son(u)} max(dp_{v,1},dp_{v,0})$,$g_{u,1}=(\sum_{v \ne son(u)} dp_{v,0}) + a_u$。

则有$dp_{u,0}=g_{u,0}+max(dp_{son(u),0}, dp_{son(u),1})$,$dp_{u,1}=g_{u,1}+dp_{son(u),0}$。

我们发现能做ddp还有一个前提是变成序列dp后可以用广义矩乘来表示转移,比如上面的例子可以表示成:

$\left[ \begin{matrix} g_{u,0} & g_{u,0} \\ g_{u,1} & -inf \end{matrix} \right] \times \left[ \begin{matrix} dp_{son(u),0} \\ dp_{son(u),1} \end{matrix} \right]=\left[ \begin{matrix} dp_{u,0} \\ dp_{u,1} \end{matrix} \right] $

然后我们就可以愉快的ddp了!要想查任意点的dp值只需查从它这个点到它所在的重链的结尾上的点的矩阵乘积即可(所以我们还需维护链尾),因为链尾的矩阵即为其dp值矩阵。

而这个$g$数组和$dp$数组都是可以在树链剖分时预处理出来的。


修改

我们发现我们只需修改$g$的值,即矩阵即可。

对于一个位置先把它的修改了,然后跳到它的链头的父亲,继续循环做直到到根。

为什么可以这么做因为$g$是与重链无关的,只用修改轻链的情况。

具体修改时需要记录前一个点(即前一条重链的联投)的dp值修改前后的增量,然后用之前的值加上其即可(因为是修改一个轻儿子)。

具体可看代码。


查询

查询时查根的dp值即可。


Code


#include
#include

#include

using namespace std;
const int N = 100010;
//输入输出
template void read(T &x) {
int f = 1; char ch = getchar(); x = 0;
for (; !isdigit(ch); ch = getchar()) if (ch == -) f = -1;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - 0;
x
*= f;
}
template
void write(T x) {
if (x > 9) write(x / 10);
putchar(x
% 10 + 0);
}
template
void print(T x) {
if (x <0) x = -x, putchar(-);
write(x);
putchar(
\n);
}
template
void cmax(T &x, T y) {if (y > x) x = y;}
template
void cmin(T &x, T y) {if (y y;}
int n;//节点个数
int m;//操作个数
int a[N];//初始值
int dp[N][2];//dp数组
//前向星
namespace QXX{
struct node{
int pre, to;
}edge[N
<<1];
int head[N], tot;
inline
void add(int u, int v) {//加边
edge[++tot].pre = head[u];
edge[tot].to
= v;
head[u]
= tot;
}
}
using namespace QXX;
//矩阵
namespace MATRIX{
struct Matrix{
int arr[3][3];
}A[N];
inline
void init(Matrix &X) {memset(X.arr, 0xcf, sizeof(X.arr));}
inline Matrix Mul(Matrix X, Matrix Y) {
Matrix Z;
init(Z);
for (int i = 1; i <= 2; i++)
for (int j = 1; j <= 2; j++)
for (int k = 1; k <= 2; k++)
cmax(Z.arr[i][j], X.arr[i][k]
+ Y.arr[k][j]);
return Z;
}
}
using namespace MATRIX;
//树链剖分
namespace TCP{
int dfn[N], num;//dfs 序
int top[N];//链头
int End[N];//链尾
int pos[N];//pos[i] 代表 dfs 序为 i 的节点是哪个
int sz[N];//子树大小
int fa[N];//父亲
int son[N];//重儿子
void dfs1(int x) {//预处理子树大小、父亲和重儿子
sz[x] = 1;
for (int i = head[x]; i; i = edge[i].pre) {
if (fa[x] == edge[i].to) continue;
fa[edge[i].to]
= x;
dfs1(edge[i].to);
sz[x]
+= sz[edge[i].to];
if (sz[edge[i].to] > sz[son[x]])
son[x]
= edge[i].to;
}
}
void dfs2(int x, int chain) {//树链剖分预处理
dfn[x] = ++num, top[x] = chain, pos[num] = x;
init(A[x]);
A[x].arr[
1][1] = A[x].arr[1][2] = 0;
A[x].arr[
2][1] = a[x];
if (son[x])
dfs2(son[x], chain), dp[x][
0] = max(dp[son[x]][1], dp[son[x]][0]), dp[x][1] = dp[son[x]][0];//加上重链信息
else
End[chain]
= dfn[x];
for (int i = head[x]; i; i = edge[i].pre) {//处理轻儿子
if (edge[i].to == fa[x] || edge[i].to == son[x])
continue;
dfs2(edge[i].to, edge[i].to);
A[x].arr[
1][1] += max(dp[edge[i].to][1], dp[edge[i].to][0]);
A[x].arr[
1][2] = A[x].arr[1][1];
A[x].arr[
2][1] += dp[edge[i].to][0];
}
dp[x][
0] += A[x].arr[1][1];
dp[x][
1] += A[x].arr[2][1];
}
}
using namespace TCP;
namespace Segment_Tree{
struct Segment{
Matrix val;
}tr[N
<<2];
inline
void push_up(int p) {tr[p].val = Mul(tr[p <<1].val, tr[p <<1 | 1].val);}
void build(int p, int l, int r) {
if (l == r) {
tr[p].val
= A[pos[l]];
return;
}
int mid = (l + r) >> 1;
build(p
<<1, l, mid);
build(p
<<1 | 1, mid + 1, r);
push_up(p);
}
void change(int p, int l, int r, int Pos) {
if (l == r) {
tr[p].val
= A[pos[l]];
return;
}
int mid = (l + r) >> 1;
if (Pos <= mid) change(p <<1, l, mid, Pos);
else change(p <<1 | 1, mid + 1, r, Pos);
push_up(p);
}
Matrix query(
int p, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return tr[p].val;
}
int mid = (l + r) >> 1;
if (R <= mid) return query(p <<1, l, mid, L, R);
else if (L > mid) return query(p <<1 | 1, mid + 1, r, L, R);
else return Mul(query(p <<1, l, mid, L, mid), query(p <<1 | 1, mid + 1, r, mid + 1, R));
}
}
using namespace Segment_Tree;
int main() {
read(n); read(m);
for (int i = 1; i <= n; ++i) read(a[i]);
for (int i = 1, u, v; i i) {
read(u); read(v);
add(u, v);
add(v, u);
}
dfs1(1);
dfs2(
1, 1);
build(
1, 1, n);
while (m--) {
int x, y;
read(x); read(y);
A[x].arr[
2][1] += y - a[x];
a[x]
= y;
while (x != 0) {
Matrix bef
= query(1, 1, n, dfn[top[x]], End[top[x]]);
change(
1, 1, n, dfn[x]);
Matrix aft
= query(1, 1, n, dfn[top[x]], End[top[x]]);
x
= fa[top[x]];
A[x].arr[
1][1] += max(aft.arr[1][1], aft.arr[2][1]) - max(bef.arr[1][1], bef.arr[2][1]);
A[x].arr[
1][2] = A[x].arr[1][1];
A[x].arr[
2][1] += aft.arr[1][1] - bef.arr[1][1];
}
Matrix ans
= query(1, 1, n, top[1], End[top[1]]);
print(max(ans.arr[
1][1], ans.arr[2][1]));
}
return 0;
}

 


推荐阅读
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
author-avatar
DaybreakCP
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有