【模板】动态 DP
最近学了一下动态dp,感觉没有想象的难。
动态DP
simple的DP是这样的:
给棵树,每个点给个权值,求一下最大权独立集。
动态DP是这样的:
给棵树,每个点给个权值还到处改,每次改的时候求一下最大权独立集。
题外话:
($NOIP2018$保卫王国)
大佬:动态$dp$板子直接秒。
神仙:这个是什么让我想一想……倍增?(然后当场切掉)
除了大佬除了神仙除了我:敲暴力。
我:(看错题了爆蛋)
几个前置知识:
(1)重链剖分
树剖时选择子树点数最多的儿子作重儿子。
重剖有几个性质:
1.终点一定是叶子。(树剖通性)
2.重链剖分序上一条重链是一个连续的区间。(树剖通性)
3.任意一个点走到根经过的轻链不超过$logn$条。(重剖特性)
4.……
(2)矩阵
著名矩乘由于满足结合律可以用来加速递推/分治。
大佬们不知道咋想的就搞出了以下运算:
$$\begin{pmatrix}a00&a01\\a10&a11\end{pmatrix}+\begin{pmatrix}b00&b01\\b10&b11\end{pmatrix} $$
$$=\begin{pmatrix}max(a00+b00,a01+b10)&max(a00+b10,a01+b11)\\max(a10+b00,a11+b10)&max(a10+b01,a11+b11)\end{pmatrix}$$
这个东西竟然满足结合律……
我们可以用他去搞一些事情了。
树的最大权独立集:
设$f[u][0/1]$表示在点$u$不取/取的情况下点$u$子树内的最大权独立集的权值。
有$dp$如下:
$f[u][0]=\sum _{fa[v]=u} max(f[v][0],f[v][1])$
$f[u][1]=w[u] + \sum _{fa[v]=u} f[v][0]$
让轻重儿子分开讨论有:
$f[u][0]= max(f[son][0],f[son][1]) + \sum _{fa[v]=u,v!=son} max(f[v][0],f[v][1])$
$f[u][1]= f[son][0] +w[u]+\sum_{fa[v]=u,v!=son}f[v][0]$
设$g[u][0]=\sum _{fa[v]=u,v!=son} max(f[v][0],f[v][1]),g[u][1]=w[u]+\sum_{fa[v]=u,v!=son}f[v][0]$
写成上面矩阵的形式是这样的:
$f[u][0] = max(g[u][0]+f[son][0],g[u][0]+f[son][1])$
$f[u][1] = max(g[u][1]+f[son][0],-inf+f[son][1])$
就是$$\begin{pmatrix}f[son][0]&f[son][1]\end{pmatrix}+\begin{pmatrix}g[u][0]&g[u][1]\\g[u][0]&-inf\end{pmatrix}$$
$$=\begin{pmatrix}f[u][0]&f[u][1]\end{pmatrix}$$
神奇。
考虑到重链剖分序重链上的点是按深度从浅到深排列的,我们应该这样(不然线段树左右要反着合并比较恶心):
$$\begin{pmatrix}g[u][0]&g[u][0]\\g[u][1]&-inf\end{pmatrix}+\begin{pmatrix}f[son][0]\\f[son][1]\end{pmatrix}$$
$$=\begin{pmatrix}f[u][0]\\f[u][1]\end{pmatrix}$$
对于每个点保留带$g$的矩阵,这样一个点的$f$矩阵就是重链上从该点的矩阵一直向下加,加到叶子结点。
区间加法用线段树平衡树等数据结构维护。
每次修改之后,由于只会修改当前点到根路径上的$f$,我们可以爆跳树链。
修改点权,当前树链上只会修改当前点的$g[1]$。
树链之间转移时,考虑轻儿子的$f$对父节点$g$的影响。
我们可以求出修改前后链顶的$f$值,然后扔到原来方程里更新父亲的$g$。
重复上述操作一直更新到根。
由于重剖$logn$条轻链,时间复杂度$O(nlog^2n)$。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100050;
const ll Inf = 0x3f3f3f3f3f3f3f3fll;
template<typename T>
inline void read(T&x)
{
T f = 1,c = 0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();}
x = f*c;
}
ll F[N][2],G[N][2],k[N];
int n,m,hed[N],cnt;
struct EG
{
int to,nxt;
}e[N<<1];
void ae(int f,int t)
{
e[++cnt].to = t;
e[cnt].nxt = hed[f];
hed[f] = cnt;
}
int dep[N],siz[N],top[N],pot[N],fa[N],son[N],tin[N],pla[N],tim;
struct mt
{
ll s[2][2];
mt(){memset(s,0,sizeof(s));}
mt(ll g0,ll g1){s[0][0]=s[0][1]=g0,s[1][0]=g1,s[1][1]=-Inf;}
mt operator + (const mt&a)const
{
mt ret;
ret.s[0][0]=max(s[0][0]+a.s[0][0],s[0][1]+a.s[1][0]);
ret.s[0][1]=max(s[0][0]+a.s[0][1],s[0][1]+a.s[1][1]);
ret.s[1][0]=max(s[1][0]+a.s[0][0],s[1][1]+a.s[1][0]);
ret.s[1][1]=max(s[1][0]+a.s[0][1],s[1][1]+a.s[1][1]);
return ret;
}
}t[N];
struct segtree
{
mt w[N<<2];
void update(int u){w[u]=w[u<<1]+w[u<<1|1];}
void build(int l,int r,int u)
{
if(l==r){w[u]=t[pla[l]]=mt(G[pla[l]][0],G[pla[l]][1]);return ;}
int mid = (l+r)>>1;
build(l,mid,u<<1);
build(mid+1,r,u<<1|1);
update(u);
}
void insert(int l,int r,int u,int qx)
{
if(l==r){w[u]=t[pla[l]];return ;}
int mid = (l+r)>>1;
if(qx<=mid)insert(l,mid,u<<1,qx);
else insert(mid+1,r,u<<1|1,qx);
update(u);
}
mt query(int l,int r,int u,int ql,int qr)
{
if(l==ql&&r==qr)return w[u];
int mid = (l+r)>>1;
if(qr<=mid)return query(l,mid,u<<1,ql,qr);
else if(ql>mid)return query(mid+1,r,u<<1|1,ql,qr);
else return query(l,mid,u<<1,ql,mid)+query(mid+1,r,u<<1|1,mid+1,qr);
}
}tr;
void dfs0(int u,int f)
{
fa[u] = f,siz[u] = 1,dep[u] = dep[f]+1;
for(int j=hed[u];j;j=e[j].nxt)
{
int to = e[j].to;
if(to==f)continue;
dfs0(to,u);siz[u]+=siz[to];
if(siz[to]>siz[son[u]])son[u]=to;
}
}
void dfs1(int u,int Top)
{
top[u] = Top,pot[u] = u,tin[u] = ++tim,pla[tim] = u;
if(son[u])dfs1(son[u],Top),pot[u]=pot[son[u]];
for(int j=hed[u];j;j=e[j].nxt)
{
int to = e[j].to;
if(to!=fa[u]&&to!=son[u])
dfs1(to,to);
}
}
void dp(int u)
{
F[u][0]=0,F[u][1]=k[u];
for(int j=hed[u];j;j=e[j].nxt)
{
int to = e[j].to;
if(to==fa[u])continue;
dp(to);
F[u][0]+=max(F[to][0],F[to][1]);
F[u][1]+=F[to][0];
}
G[u][0] = F[u][0] - max(F[son[u]][0],F[son[u]][1]);
G[u][1] = F[u][1] - F[son[u]][0];
}
mt get_mt(int x){return tr.query(1,n,1,tin[top[x]],tin[pot[x]]);}
void chg(int x,int y)
{
t[x].s[1][0] += y-k[x],k[x] = y;
while(x)
{
mt m0 = get_mt(x);
tr.insert(1,n,1,tin[x]);
mt m1 = get_mt(x);
x = fa[top[x]];
if(!x)break;
t[x].s[0][0]+=max(m1.s[0][0],m1.s[1][0])-max(m0.s[0][0],m0.s[1][0]);
t[x].s[0][1]=t[x].s[0][0];
t[x].s[1][0]+=m1.s[0][0]-m0.s[0][0];
}
}
int main()
{
// freopen("tt.in","r",stdin);
read(n),read(m);
for(int i=1;i<=n;i++)
read(k[i]);
for(int u,v,i=1;i<n;i++)
read(u),read(v),ae(u,v),ae(v,u);
dfs0(1,0),dfs1(1,1);
dp(1);tr.build(1,n,1);
for(int x,y,i=1;i<=m;i++)
{
read(x),read(y);
chg(x,y);mt now = get_mt(1);
printf("%lld\n",max(now.s[0][0],now.s[1][0]));
}
return 0;
}
附保卫王国代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100050;
const ll inf = 1e16;
template<typename T>
inline void read(T&x)
{
T f = 1,c = 0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();}
x = f*c;
}
int n,m,hed[N],cnt;
ll p[N],F[N][2],G[N][2];
char op[10];
struct EG
{
int to,nxt;
}e[N<<1];
void ae(int f,int t)
{
e[++cnt].to = t;
e[cnt].nxt = hed[f];
hed[f] = cnt;
}
int dep[N],siz[N],fa[N],son[N],top[N],pot[N],tin[N],pla[N],tim;
void dfs0(int u,int f)
{
fa[u] = f,siz[u] = 1,dep[u] = dep[f]+1;
for(int j=hed[u];j;j=e[j].nxt)
{
int to = e[j].to;
if(to==f)continue;
dfs0(to,u);
siz[u]+=siz[to];
if(siz[to]>siz[son[u]])son[u]=to;
}
}
void dfs1(int u,int Top)
{
top[u] = Top,tin[u] = ++tim,pla[tim] = u;
if(son[u])dfs1(son[u],Top),pot[u]=pot[son[u]];
else pot[u] = u;
for(int j=hed[u];j;j=e[j].nxt)
{
int to = e[j].to;
if(to!=fa[u]&&to!=son[u])
dfs1(to,to);
}
}
void dp(int u)
{
F[u][0] = 0,F[u][1] = p[u];
for(int j=hed[u];j;j=e[j].nxt)
{
int to = e[j].to;
if(to==fa[u])continue;
dp(to);
F[u][0] += F[to][1];
F[u][1] += min(F[to][0],F[to][1]);
}
G[u][0] = F[u][0] - F[son[u]][1];
G[u][1] = F[u][1] - min(F[son[u]][0],F[son[u]][1]);
}
struct mt
{
ll s[2][2];
mt(){memset(s,0,sizeof(s));}
mt(ll g0,ll g1){s[0][0]=inf,s[0][1]=g0,s[1][0]=s[1][1]=g1;}
mt operator + (const mt&a)const
{
mt ret;
ret.s[0][0]=min(s[0][0]+a.s[0][0],s[0][1]+a.s[1][0]);
ret.s[0][1]=min(s[0][0]+a.s[0][1],s[0][1]+a.s[1][1]);
ret.s[1][0]=min(s[1][0]+a.s[0][0],s[1][1]+a.s[1][0]);
ret.s[1][1]=min(s[1][0]+a.s[0][1],s[1][1]+a.s[1][1]);
return ret;
}
}t[N];
struct segtree
{
mt w[N<<2];
void update(int u){w[u]=w[u<<1]+w[u<<1|1];}
void build(int l,int r,int u)
{
if(l==r){w[u]=t[pla[l]]=mt(G[pla[l]][0],G[pla[l]][1]);return ;}
int mid = (l+r)>>1;
build(l,mid,u<<1),build(mid+1,r,u<<1|1);
update(u);
}
void insert(int l,int r,int u,int qx)
{
if(l==r){w[u]=t[pla[l]];return ;}
int mid = (l+r)>>1;
if(qx<=mid)insert(l,mid,u<<1,qx);
else insert(mid+1,r,u<<1|1,qx);
update(u);
}
mt query(int l,int r,int u,int ql,int qr)
{
if(l==ql&&r==qr)return w[u];
int mid = (l+r)>>1;
if(qr<=mid)return query(l,mid,u<<1,ql,qr);
else if(ql>mid)return query(mid+1,r,u<<1|1,ql,qr);
else return query(l,mid,u<<1,ql,mid)+query(mid+1,r,u<<1|1,mid+1,qr);
}
}tr;
mt get_mt(int x){return tr.query(1,n,1,tin[top[x]],tin[pot[x]]);}
void chg(int a,int x)
{
if(x==1)t[a].s[1][0]-=inf;
else t[a].s[1][0]+=inf;
t[a].s[1][1]=t[a].s[1][0];
}
void upd(int x)
{
while(x)
{
mt m0 = get_mt(x);
tr.insert(1,n,1,tin[x]);
mt m1 = get_mt(x);
x = fa[top[x]];
if(!x)break;
t[x].s[0][1]+=m1.s[1][1]-m0.s[1][1];
t[x].s[1][0]+=min(m1.s[0][1],m1.s[1][1])-min(m0.s[0][1],m0.s[1][1]);
t[x].s[1][1]=t[x].s[1][0];
}
}
int main()
{
// freopen("tt.in","r",stdin);
read(n),read(m);scanf("%s",op);
for(int i=1;i<=n;i++)read(p[i]);
for(int u,v,i=1;i<n;i++)read(u),read(v),ae(u,v),ae(v,u);
dfs0(1,0),dfs1(1,1);dp(1);tr.build(1,n,1);
for(int a,x,b,y,i=1;i<=m;i++)
{
read(a),read(x),read(b),read(y);
if((fa[a]==b||fa[b]==a)&&!x&&!y){puts("-1");continue;}
chg(a,x);
upd(a);
chg(b,y);
upd(b);
mt M = get_mt(1);
ll ans = min(M.s[0][1],M.s[1][1]);
ans+=(x+y)*inf;
printf("%lld\n",ans);
chg(a,!x);upd(a);
chg(b,!y);upd(b);
}
return 0;
}
保卫王国
【模板】动态 DP的更多相关文章
- [模板] 动态dp
用途 对于某些树形dp(目前只会树上最大权独立集或者类似的),动态地修改点权,并询问修改后的dp值 做法(树剖版) 以最大权独立集为例 设$f[x][0/1]$表示x选不选,这棵子树的最大权独立集大小 ...
- [luogu 4719][模板]动态dp
传送门 Solution \(f_{i,0}\) 表示以i节点为根的子树内,不选i号节点的最大独立集 \(f_{i,1}\)表示以i节点为根的子树内,选i号节点的最大独立集 \(g_{i,0}\) 表 ...
- LG4719 【模板】动态dp 及 LG4751 动态dp【加强版】
题意 题目描述 给定一棵\(n\)个点的树,点带点权. 有\(m\)次操作,每次操作给定\(x,y\),表示修改点\(x\)的权值为\(y\). 你需要在每次操作之后求出这棵树的最大权独立集的权值大小 ...
- 洛谷P4719 【模板】"动态 DP"&动态树分治
[模板]"动态 DP"&动态树分治 第一道动态\(DP\)的题,只会用树剖来做,全局平衡二叉树什么的就以后再学吧 所谓动态\(DP\),就是在原本的\(DP\)求解的问题上 ...
- Luogu P4643 【模板】动态dp
题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ...
- 洛谷P4719 【模板】动态dp(ddp LCT)
题意 题目链接 Sol 动态dp板子题.有些细节还没搞懂,待我研究明白后再补题解... #include<bits/stdc++.h> #define LL long long using ...
- 【洛谷】P4643 【模板】动态dp
题解 在冬令营上听到冬眠的东西,现在都是板子了猫锟真的是好毒瘤啊(雾) (立个flag,我去thusc之前要把WC2018T1乱搞过去= =) 好的,我们可以参考猫锟的动态动态dp的课件,然后你发现你 ...
- 「LGP4719【模板】动态dp」
题目 尽管知道这个东西应该不会考了,但是还是学一学吧 哎要是去年noip之前学该多好 动态\(dp\)就是允许修改的一个\(dp\),比如这道题,我们都知道这是一个树上最大点权独立集 众所周知方程长这 ...
- 洛谷4719 【模板】动态dp
题目:https://www.luogu.org/problemnew/show/P4719 关于动态DP似乎有猫锟的WC2018论文,但找不见:还是算了. http://immortalco.blo ...
- P4719 【模板】动态dp
\(\color{#0066ff}{ 题目描述 }\) 给定一棵\(n\)个点的树,点带点权. 有\(m\)次操作,每次操作给定\(x,y\),表示修改点xx的权值为\(y\). 你需要在每次操作之后 ...
随机推荐
- C++ 实现 Parsec
前一段时间看到了梨梨喵聚聚写的Parser Combinator 在 C++ 里的 DSL, 感觉好厉害, 正好毕设里要写一部分前端, 昨天又把这篇文章看了一遍, 想着我也要用这么酷炫的东西来参与一下 ...
- Solution -「洛谷 P4320」道路相遇
\(\mathcal{Description}\) Link. 给定一个 \(n\) 个点 \(m\) 条边的连通无向图,并给出 \(q\) 个点对 \((u,v)\),询问 \(u\) 到 ...
- 利用脚本快速执行Dockerfile以及docker镜像的启停与删除
`关于脚本:` 'sh setup.sh build' # 将Dockerfile创建成镜像 'sh setup.sh run' # 启动build创建好的镜像,放到后台运行 'sh setup.sh ...
- 『无为则无心』Python面向对象 — 52、私有成员方法(类中行为的封装)
Python对于类的成员没有严格的访问控制限制,这与其他面向对象的编程语言是有所区别的. 关于私有方法其实和私有属性差不多,有如下要点: 1.通常我们约定,两个下划线开头的方法是私有方法. 2.类内部 ...
- NSSCTF-easyupload1.0
2.0和3.0已经说过,这个1.0上传也可以使用那两个题目的做法,就是使用htaccess的方式进行定义上传文件的类型来进行连接shell,1.0肯定是没有2.0和3.0难的,所以说,也不要将这个题目 ...
- 基于IPv6的RIPng路由协议测试——信而泰网络测试仪实操
关键词 IPv6; RIPng; 协议测试; 内部网关协议; 外部网关协议 前言:在国际性网络中,如因特网,拥有很多应用于整个网络的路由选择协议.形成网络的每一个自治系统(AS),都有属于自己的路由选 ...
- bool? int?等可为空的数值类型的运算 三值逻辑
算术运算:(+,-,*,/)时,只要一个为null,则结果为null. 比较运算符: <.>.<= 和 >=,也是如此.如果一个或全部两个操作数都为 null,则结果为 fal ...
- 带你掌握Redis数据类型:string和Hash
摘要:Redis中有五大数据类型,分别是String.List.Set.Hash和Zset. 本文分享自华为云社区<Redis的string类型常用命令解析>,作者:灰小猿 . 先问大家一 ...
- Java、Python语法区别,不断更新
基本语句.文件方面 Java中的字符是单引号,字符串是双引号:Python则是单双都可以 Java语句结束有分号;,Python没有(写分号也正确) Java中程序执行需要有main函数,Python ...
- in memory computing 存内计算是学术圈自娱自乐还是真有价值?
如果单从初衷和预想的价值来看,还是很诱人的.在冯诺依曼体系中,cpu计算和memory存储是分离的,而两者之间的data movement会造成高延迟和高耗能. 关于PIM类似的思想在50年前曾有人提 ...