模版 动态 dp

终于来写这个东西了。。

LG 模版:给定 n 个点的数,点有点权, $ m $ 次修改点权,求修改完后这个树的最大独立集大小。

我们先来考虑朴素的最大独立集的 dp

\[dp[u][0] = \sum_v max\{dp[v][1],dp[v][0]\}\\dp[u][1] = val[u] + \sum_v dp[v][0]
\]

现在我们就拥有了一个 $ O(nm) $ 的做法,但是明显它不优秀。

既然支持修改,我们可以考虑树剖(或者LCT),现在树被我们剖成了重链和轻链。此时发现我们其实可以先对轻子树算出 dp 值并且累计到当前根,再去算重链的影响,这样有什么好处呢?好处在于重链我们可以单独算,这样的话 dp 转移就是连续的。同时,当你修改一个点,它所影响的也仅仅是它到根的很多重链,不会影响到这路径上虚儿子的贡献。

但是如果按照原来的 dp 转移,修改点权仍然很困难。为此定义了一种新的矩阵乘法,让两个矩阵相乘时的乘法改成加法,加法改成最大值,也就是:

\[(A\times B)_{i,j} = \text{MAX}_k\{A_{i,k} + B_{k,j}\}
\]

不难发现这样定义矩阵乘法后矩阵乘法仍然满足结合律。

然后我们考虑对于一个点 $ u $ ,假设它已经算完了轻儿子的贡献,计作 $ g[u][0/1] $ ,考虑从重儿子转移,那么这个点的转移矩阵就是

\[\begin{bmatrix} g[u][0]&g[u][0]\\g[u][1]&-\infin \end{bmatrix}\times \begin{bmatrix}dp[u][0]\\dp[u][1]\end{bmatrix}
\]

为什么呢?考虑 $ dp[u][0] $ 可以从什么转移过来,是由 $ dp[u][0] $ 和 $ dp[u][1] $ 中选择较大的和 $ g[u][0] $ 加起来得到的,而 $ dp[u][1] $ 由 $ dp[u][0] + g[u][1] $ 得到,故最后一个位置填 $ -\infin $。

写到这里感觉 LCT 会比树剖好写的多,所以后面默认使用 LCT 了。

然后我们考虑对每个链维护它的转移矩阵的乘积。而 $ g $ 的维护就是对一个链的顶端的父亲更新 $ g $ 即可,体现在 LCT 中就是一个点维护它 splay 里面的矩阵的积。注意这里的矩阵乘法的顺序,对一个链做的时候应该从上乘到下,所以 pushup 的时候应该先左后中最后右。

如果我们需要得到一个点的 dp 值,必须把它 Access 并且 旋转到根,类似 Qtree V 的做法,不然它内部的值是假的(是子树或者splay子树内的乘积).

修改权值,比较简单的方法是 LCT 直接 旋转到根 然后直接修改 val 和矩阵就可以了。

感觉比 Red Blue Tree 好写,不用拿 BST 维护虚儿子信息。。(然后估计码一天)

代码还是很好看的(虽然调起来很烦)

#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
using namespace std;
#define MAXN 100006
#define max( a , b ) ( (a) > (b) ? (a) : (b) )
#define inf 0x3f3f3f3f
int n , m;
int w[MAXN]; int head[MAXN] , to[MAXN << 1] , nex[MAXN << 1] , ecn;
void ade( int u , int v ) {
to[++ ecn] = v , nex[ecn] = head[u] , head[u] = ecn;
} struct mtrx {
#define N 2
int A[2][2];
inline void in( int a , int b ) { A[0][0] = A[0][1] = a , A[1][0] = b , A[1][1] = -inf; }
inline int re( ) { return max( A[0][0] , A[1][0] ); }
inline mtrx operator * ( const mtrx& a ) const {
mtrx ret;
for( int i = 0 ; i < 2 ; ++ i ) for( int j = 0 ; j < 2 ; ++ j )
ret.A[i][j] = max( A[i][0] + a.A[0][j] , A[i][1] + a.A[1][j] );
return ret;
}
};
int ch[MAXN][2] , fa[MAXN] , dp[MAXN][2];
mtrx G[MAXN];
inline bool notroot( int u ) {
return ch[fa[u]][0] == u || ch[fa[u]][1] == u;
}
inline void pu( int u ) {
G[u].in( u[dp][0] , u[dp][1] );
if( ch[u][0] ) G[u] = G[ch[u][0]] * G[u];
if( ch[u][1] ) G[u] = G[u] * G[ch[u][1]];
}
inline void rotate( int u ) {
int f = fa[u] , g = fa[f] , w = ch[f][1]==u , k = ch[u][w^1];
if(notroot( f )) ch[g][ch[g][1]==f] = u;ch[f][w] = k , ch[u][w^1] = f;
fa[f] = u , fa[k] = f , fa[u] = g;
pu( f ) , pu( u );
}
//void rotate( int x ) {
// int f = fa[x] , g = fa[f] , w = ch[fa[x]][1] == x;
// int wf = ch[g][1]==f , k = ch[x][w^1];
// if( notroot(f) ) ch[g][wf] = x; ch[f][w] = k , ch[x][w^1] = f;
// fa[f] = x , fa[k] = f , fa[x] = g;
// pu( f ) , pu( x );
//}
void splay( int x ) {
int f , g;
while( notroot( x ) ) {
f = fa[x] , g = fa[f];
if( notroot( f ) )
rotate( (ch[f][0]==x)^(ch[g][0]==f) ? x : f );
rotate( x );
}
}
void access( int x ) {
for( int p = 0 ; x ; ( p = x , x = fa[x] ) ) {
splay(x);
if( ch[x][1] ) // Heavy -> Light
x[dp][0] += G[ch[x][1]].re() , x[dp][1] += G[ch[x][1]].A[0][0];
if( p ) // Light -> Heavy
x[dp][0] -= G[p].re() , x[dp][1] -= G[p].A[0][0];
ch[x][1] = p;
pu(x);
}
}
void pre( int u , int f ) {
dp[u][1] = w[u];
for( int i = head[u] ; i ; i = nex[i] ) {
int v = to[i];
if( v == f ) continue;
pre( v , u );
fa[v] = u;
u[dp][0] += max( v[dp][0] , v[dp][1] );
u[dp][1] += v[dp][0];
}
G[u].in( u[dp][0] , u[dp][1] );
} int main() {
// freopen("in.in","r",stdin);
cin >> n >> m;
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",&w[i]);
for( int i = 1 , u , v ; i < n ; ++ i )
scanf("%d%d",&u,&v) , ade( u , v ) , ade( v , u );
pre( 1 , 1 );
// for( int i = 1 ; i <= n ; ++ i ) printf("%d\n",G[i].re());
int u , v;
while( m-- ) {
scanf("%d%d",&u,&v);
access( u );
splay( u );
dp[u][1] += v - w[u] , w[u] = v;
pu( u );
printf("%d\n",G[u].re());
// for( int i = 1 ; i <= n ; ++ i ) printf("%d ",fa[i]);
// puts("");
}
}

一个例题 NOIP 2018 保卫王国

其实就是板子题,每次询问,强制选本质上就是权值 inf 强制不选择就是 -inf

中间挂了几次。。这题 longlong 得注意。。(矩阵返回值没开LongLong然后wa了两发。。)

#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
using namespace std;
#define MAXN 400006
#define max( a , b ) ( (a) > (b) ? (a) : (b) )
#define inf (1ll<<60)
int n , m;
int w[MAXN]; int head[MAXN] , to[MAXN << 1] , nex[MAXN << 1] , ecn;
void ade( int u , int v ) {
to[++ ecn] = v , nex[ecn] = head[u] , head[u] = ecn;
} struct mtrx {
#define N 2
long long A[2][2];
inline void in( long long a , long long b ) { A[0][0] = A[0][1] = a , A[1][0] = b , A[1][1] = -inf; }
inline long long re( ) { return max( A[0][0] , A[1][0] ); }
inline mtrx operator * ( const mtrx& a ) const {
mtrx ret;
for( int i = 0 ; i < 2 ; ++ i ) for( int j = 0 ; j < 2 ; ++ j )
ret.A[i][j] = max( A[i][0] + a.A[0][j] , A[i][1] + a.A[1][j] );
return ret;
}
};
int ch[MAXN][2] , fa[MAXN]; long long dp[MAXN][2];
mtrx G[MAXN];
inline bool notroot( int u ) {
return ch[fa[u]][0] == u || ch[fa[u]][1] == u;
}
inline void pu( int u ) {
G[u].in( u[dp][0] , u[dp][1] );
if( ch[u][0] ) G[u] = G[ch[u][0]] * G[u];
if( ch[u][1] ) G[u] = G[u] * G[ch[u][1]];
}
inline void rotate( int u ) {
int f = fa[u] , g = fa[f] , w = ch[f][1]==u , k = ch[u][w^1];
if(notroot( f )) ch[g][ch[g][1]==f] = u;ch[f][w] = k , ch[u][w^1] = f;
fa[f] = u , fa[k] = f , fa[u] = g;
pu( f ) , pu( u );
}
void splay( int x ) {
int f , g;
while( notroot( x ) ) {
f = fa[x] , g = fa[f];
if( notroot( f ) )
rotate( (ch[f][0]==x)^(ch[g][0]==f) ? x : f );
rotate( x );
}
}
void access( int x ) {
for( int p = 0 ; x ; ( p = x , x = fa[x] ) ) {
splay(x);
if( ch[x][1] ) // Heavy -> Light
x[dp][0] += G[ch[x][1]].re() , x[dp][1] += G[ch[x][1]].A[0][0];
if( p ) // Light -> Heavy
x[dp][0] -= G[p].re() , x[dp][1] -= G[p].A[0][0];
ch[x][1] = p;
pu(x);
}
}
void pre( int u , int f ) {
dp[u][1] = w[u];
for( int i = head[u] ; i ; i = nex[i] ) {
int v = to[i];
if( v == f ) continue;
pre( v , u );
fa[v] = u;
u[dp][0] += max( v[dp][0] , v[dp][1] );
u[dp][1] += v[dp][0];
}
G[u].in( u[dp][0] , u[dp][1] );
}
long long S;
void mdfy( int u , long long x ) {
access( u ) , splay( u );
dp[u][1] += x;
pu( u );
}
int main() {
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
cin >> n >> m;
scanf("%*s");
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",&w[i]) , S += w[i];
for( int i = 1 , u , v ; i < n ; ++ i )
scanf("%d%d",&u,&v) , ade( u , v ) , ade( v , u );
pre( 1 , 1 );
// for( int i = 1 ; i <= n ; ++ i ) printf("%d\n",G[i].re());
int u , v , x1 , x2;
while( m-- ) {
scanf("%d%d%d%d",&u,&x1,&v,&x2);
mdfy( u , x1 ? -inf : inf ) , mdfy( v , x2 ? -inf : inf );
S += ( ( x1 ^ 1 ) + ( x2 ^ 1 ) ) * inf;
printf("%lld\n",S - G[v].re() > 1e10 ? -1 : S - G[v].re());
S -= ( ( x1 ^ 1 ) + ( x2 ^ 1 ) ) * inf;
mdfy( u , x1 ? inf : -inf ) , mdfy( v , x2 ? inf : -inf );
}
}

模版 动态 dp的更多相关文章

  1. 动态DP之全局平衡二叉树

    目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ...

  2. Luogu P4643 【模板】动态dp

    题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ...

  3. 动态dp学习笔记

    我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...

  4. 洛谷P4719 动态dp

    动态DP其实挺简单一个东西. 把DP值的定义改成去掉重儿子之后的DP值. 重链上的答案就用线段树/lct维护,维护子段/矩阵都可以.其实本质上差不多... 修改的时候在log个线段树上修改.轻儿子所在 ...

  5. 动态 DP 学习笔记

    不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ...

  6. 动态dp初探

    动态dp初探 动态区间最大子段和问题 给出长度为\(n\)的序列和\(m\)次操作,每次修改一个元素的值或查询区间的最大字段和(SP1714 GSS3). 设\(f[i]\)为以下标\(i\)结尾的最 ...

  7. [总结] 动态DP学习笔记

    学习了一下动态DP 问题的来源: 给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小. 首先一个显然的 \(O(nm)\) 的做法就 ...

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

    题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一 ...

  9. [复习]动态dp

    [复习]动态dp 你还是可以认为我原来写的动态dp就是在扯蛋. [Luogu4719][模板]动态dp 首先作为一个\(dp\)题,我们显然可以每次修改之后都进行暴力\(dp\),设\(f[i][0/ ...

随机推荐

  1. Python语法1

    变量 命名规则 变量名必须是大小写英文字母.数字或下划线 _ 的组合,不能用数字开头,并且对大小写敏感 变量赋值 同一变量可以反复赋值,而且可以是不同类型的变量 i=2; i="name&q ...

  2. HTTP请求如何带参

    这两天正好作一份API的接口文档,关于HTTP request如何传递参数不是很清楚,这里转载了他人的文档,让我明白了很多.. http://tomfish88.iteye.com/category/ ...

  3. spring social实现百度登录

    在早期我写过一篇spring social理解的文章,介绍了一些spring social的概念,但是没有提供一个例子.在这篇博客中,提供一个简单的spring social的例子,实现 百度登录,那 ...

  4. Noip模拟35 2021.8.10

    考试题目变成四道了,貌似确实根本改不完... 不过给了两个小时颓废时间确实很爽(芜湖--) 但是前几天三道题改着不是很费劲的时候为什么不给放松时间, 非要在改不完题的时候颓?? 算了算了不碎碎念了.. ...

  5. C/C++编程笔记:浪漫流星雨表白装b程序

    作为一个未来可能会成为一个专业程序员的小伙们,不知道你们现在学到哪里了,学了点东西之后有没有想在你女朋友面前装个大大的b呢,今天小编就给你一个机会来研究一下下边的代码吧,保证大写的N,当然大佬是排除在 ...

  6. 21.7.31 test

    \(NOIP\) 测试 好久没有这种感觉能阿克的冲动了!但还是挂了分 T1 WOJ2608(模拟,拓扑排序) 签到题,直接模拟,有点像拓扑排序. 要给点打标记不然可能被某次操作中弹出多次该点导致WA ...

  7. IDEA插件开发,我是如何把公司的发布系统搬到IDEA里的

    不得不说JetBrains公司直的非常的牛B,每一个程序员都能在JetBrains的官方网站找到一款属于自己的开发工具.这些开发工具在工作中给我们带来了巨大的便利.各种各样的基础插件,第三方插件,真是 ...

  8. telnet IP 端口 的作用

    测试远程服务器的端口是否开启

  9. 远程连接linux | Xshell和Xftp下载安装

    为什么需要远程登录linux 公司开发时候, 具体的情况是这样的: Linux 一般作为服务器使用,而服务器一般放在机房,你不可能在机房操作你的 Linux 服务器.这时我们就需要远程登录到Linux ...

  10. SpringCloud config native 配置

    1.概述 最近项目使用springCloud 框架,使用config搭建git作为配置中心. 在私有化部署中,出现很多比较麻烦的和鸡肋的设计. 每次部署都需要安装gitlab 有些环境安装完gitla ...