动态DP
更新日志
2025/02/03:开工。2025/02/04:大幅修正。
2025/02/09:重构,完工。
问题
动态DP通常用来解决动态更改数值并求DP值。
思路
首先我们常规地得出DP方程。
这并不能满足快速修改的需要。所以我们考虑储存状态转移信息,然后借助线段树维护整个区间的状态转移信息,这样我们就可以进行单点修改操作,并通过区间查询获取DP值。
如果转移信息不太好维护,考虑借助矩阵维护信息。这是一个常见的解决方案。
常见情景
动态DP通常用来解决若只的树上DP问题套上毒瘤的单点修改问题。
通常来说,树型DP单点的修改往往会影响其到根节点的路径的DP值。
很显然的,我们考虑重链剖分,并对剖分之后的 dfn 序建立线段树。
在修改的时候,我们不断跳轻链,在每一条重链的第一次到达的节点更新信息,就完成了到根路径的修改。查询的话,通常情况下树型DP都是从下往上的,所以我们考虑当前节点到其所在重链的末尾节点的转移信息和(在dfn序下,是连续的一段区间),就是它的DP值。
这样复杂度是 \(O(n\log^2n)\) 的。
利用全局平衡二叉树可以做到 \(O(n\log n)\) 的复杂度,详见链接页面。
例题
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define per(i,x,y) for(int i=(x);i>=(y);i--)
#define repl(i,x,y) for(int i=(x);i<(y);i++)
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7/*998244353*/;
const int N=1e5+5;
int n,m;
int a[N];
struct matrix{
int m[2][2];
matrix(){memset(m,-0x3f,sizeof(m));}
};
matrix operator*(matrix a,matrix b){
matrix c;
repl(i,0,2)repl(j,0,2)repl(k,0,2)chmax(c.m[i][j],a.m[i][k]+b.m[k][j]);
return c;
}
matrix val[N];
struct segment{
matrix dat[N<<2];
void update(int x){
dat[x]=dat[x<<1]*dat[x<<1|1];
}
void build(int x=1,int l=1,int r=n){
if(l==r){
dat[x]=val[l];
return;
}
int m=l+r>>1;
build(x<<1,l,m);
build(x<<1|1,m+1,r);
update(x);
}
void change(int k,int x=1,int l=1,int r=n){
if(l==r){
dat[x]=val[l];
return;
}
int m=l+r>>1;
if(k<=m)change(k,x<<1,l,m);
else change(k,x<<1|1,m+1,r);
update(x);
}
matrix query(int lq,int rq,int x=1,int l=1,int r=n){
if(lq<=l&&r<=rq)return dat[x];
int m=l+r>>1;
if(lq<=m&&m<rq){
return query(lq,rq,x<<1,l,m)*query(lq,rq,x<<1|1,m+1,r);
}else{
if(lq<=m)return query(lq,rq,x<<1,l,m);
else return query(lq,rq,x<<1|1,m+1,r);
}
}
}seg;
vec<int> g[N];
int f[N][2];
int dcnt;
int dfn[N],siz[N],son[N];
int fa[N],tp[N],en[N];
void dfs1(int now,int fid){
siz[now]=1;
fa[now]=fid;
for(auto nxt:g[now]){
if(nxt==fid)continue;
dfs1(nxt,now);
siz[now]+=siz[nxt];
if(siz[nxt]>siz[son[now]])son[now]=nxt;
}
}
void dfs2(int now,int top){
dfn[now]=++dcnt;
tp[now]=top;
val[dfn[now]].m[0][0]=f[now][0]=0;
val[dfn[now]].m[1][0]=f[now][1]=a[now];
val[dfn[now]].m[0][1]=0;
if(son[now]){
dfs2(son[now],tp[now]);
en[now]=en[son[now]];
f[now][0]+=max(f[son[now]][0],f[son[now]][1]);
f[now][1]+=f[son[now]][0];
}else en[now]=now;
for(auto nxt:g[now]){
if(nxt==fa[now]||nxt==son[now])continue;
dfs2(nxt,nxt);
f[now][0]+=max(f[nxt][0],f[nxt][1]);
f[now][1]+=f[nxt][0];
val[dfn[now]].m[0][1]=(val[dfn[now]].m[0][0]+=max(f[nxt][0],f[nxt][1]));
val[dfn[now]].m[1][0]+=f[nxt][0];
}
}
void changepath(int x,int y){
val[dfn[x]].m[1][0]+=y-a[x];a[x]=y;
matrix fst,snd;
while(1){
fst=seg.query(dfn[tp[x]],dfn[en[x]]);
seg.change(dfn[x]);
snd=seg.query(dfn[tp[x]],dfn[en[x]]);
x=fa[tp[x]];
if(!x)break;
val[dfn[x]].m[0][1]=(val[dfn[x]].m[0][0]+=max(snd.m[0][0],snd.m[1][0])-max(fst.m[0][0],fst.m[1][0]));
val[dfn[x]].m[1][0]+=snd.m[0][0]-fst.m[0][0];
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
rep(i,1,n)cin>>a[i];
rep(i,1,n-1){
int a,b;cin>>a>>b;
g[a].pub(b);g[b].pub(a);
}
dfs1(1,0);dfs2(1,1);
seg.build();
rep(i,1,m){
int x,y;
cin>>x>>y;
changepath(x,y);
matrix res=seg.query(dfn[1],dfn[en[1]]);
cout<<max(res.m[0][0],res.m[1][0])<<"\n";
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】