模版 动态 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. Java中的函数式编程(四)方法引用method reference

    写在前面 我们已经知道,lambda表达式是一个匿名函数,可以用lambda表达式来实现一个函数式接口.   很自然的,我们会想到类的方法也是函数,本质上和lambda表达式是一样的,那是否也可以用类 ...

  2. kafka错误之 Topic xxx not present in metadata after 60000 ms

    Topic xxx not present in metadata after 60000 ms 一.背景 二.场景还原 1.jar包引入 2.jar代码 3.运行结果 三.问题解决 四.参考文档 一 ...

  3. 编译内核错误:Can't use 'defined(@array)' (Maybe you should just omit the defined()?) at kernel/timeconst.pl line 373

    最近在编译一个新的rk sdk的时候,编译内核报错 CHK include/linux/version.h CHK include/generated/utsrelease.h make[1]: 'i ...

  4. python3.5 安装mysqlclient

    python 3.5 安装 mysqlclient 会失败 pip install mysqlclient 注意这里环境中只有python3.5 会出现一大堆红字 编译终止, error: comma ...

  5. 空格替换 牛客网 程序员面试金典 C++ Python

    空格替换 牛客网 程序员面试金典 C++ Python 题目描述 请编写一个方法,将字符串中的空格全部替换为"%20".假定该字符串有足够的空间存放新增的字符,并且知道字符串的真实 ...

  6. 攻防世界Web之fakebook

    打开题目,得到一个网页,包含一个表格.两个按钮. 习惯性先查看网页源码,但没发现有效信息. <!doctype html> <html lang="ko"> ...

  7. Laravel/Lumen 分组求和问题 where groupBy sum

    在Laravel中使用分组求和,如果直接使用Laravel各数据库操作方法,应该会得出来如下代码式: DB::table('table_a') ->where('a','=',1) ->g ...

  8. Ubuntu 安装 mysql 报错 "update-alternatives: 错误: 候选项路径 /etc/mysql/mysql.cnf 不存在"

    解决方法: sudo cp /etc/mysql/my.cnf /etc/mysql/mysql.cnf 偷梁换柱-! 如果想更新mysql的源方法如下: wget http://dev.mysql. ...

  9. 【Python+postman接口自动化测试】(7)Postman 的使用教程

    Postman v6的使用 Postman: 简单方便的接口调试工具,便于分享和协作.具有接口调试,接口集管理,环境配置,参数化,断言,批量执行,录制接口,Mock Server, 接口文档,接口监控 ...

  10. 开源项目|Go 开发的一款分布式唯一 ID 生成系统

    原文连接: 开源项目|Go 开发的一款分布式唯一 ID 生成系统 今天跟大家介绍一个开源项目:id-maker,主要功能是用来在分布式环境下生成唯一 ID.上周停更了一周,也是用来开发和测试这个项目的 ...