[LuoguP4719][模板]动态DP(动态DP)
[LuoguP4719][模板]动态DP(动态DP)
题面
给出一棵n个点的树,点带权。m组修改,每次修改一个点的点权,并询问整棵树最大权独立集大小。
分析
约定:child(x)表示x的儿子集合,son(x)表示x的重儿子。
先写出树形DP.设fx,0/1表示不选或选x,x的子树里最大权独立集的大小.
如果不选x,那么儿子y可以任意选
fx,0=∑y∈child(x)max(fy,0,fy,1)
如果选了x,那么儿子y就不能选。
fx,1=valx+∑y∈child(x)fy,0
动态DP的套路,轻链和重链分别维护,令:
gx,0=∑y∈child(x)−{son(x)}max(fy,0,fy,1)
gx,1=valx+∑y∈child(x)−{son(x)}fy,0
那么有:
fx,0=max(fson(x),0,fson(x),1)+gx,0
fx,1=fson(x),0+gx,1
写成矩阵的形式(注意这里是max,+矩阵乘法)
[fx,0fx,1]=[gx,0 gx,0gx,1 −∞][fson(x),0fson(x),1]
然后按照动态DP的套路,沿着重链往上跳,修改链头尾节点即可。对于路径查询矩阵乘积,可以用树链剖分+线段树或LCT实现。
代码
树链剖分+线段树:
#include<iostream> #include<cstdio> #include<cstring> #define INF 0x3f3f3f3f #define maxn 200000 using namespace std; typedef long long ll; int n,m; struct edge { int from; int to; int next; } E[maxn*2+5]; int head[maxn+5]; int esz=1; void add_edge(int u,int v) { esz++; E[esz].from=u; E[esz].to=v; E[esz].next=head[u]; head[u]=esz; } int fa[maxn+5],son[maxn+5],sz[maxn+5],top[maxn+5],btm[maxn+5]/*所在重链最底端*/,dfn[maxn+5],hash_dfn[maxn+5]; void dfs1(int x,int f) { sz[x]=1; fa[x]=f; for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=f) { dfs1(y,x); sz[x]+=sz[y]; if(sz[y]>sz[son[x]]) son[x]=y; } } } int tim=0; void dfs2(int x,int t) { top[x]=t; dfn[x]=++tim; hash_dfn[dfn[x]]=x; if(son[x]) { dfs2(son[x],t); btm[x]=btm[son[x]];//维护重链最底端节点 } else btm[x]=x; for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=fa[x]&&y!=son[x]) { dfs2(y,y); } } } struct matrix { ll a[2][2]; inline void set(int x) { for(int i=0; i<2; i++) { for(int j=0; j<2; j++) a[i][j]=x; } } friend matrix operator * (matrix p,matrix q) { matrix ans; ans.set(-INF); for(int i=0; i<2; i++) { for(int j=0; j<2; j++) { for(int k=0; k<2; k++) { ans.a[i][j]=max(ans.a[i][j],p.a[i][k]+q.a[k][j]); } } } return ans; } } mat[maxn+5]; ll val[maxn+5]; ll f[maxn+5][2],g[maxn+5][2]; void dfs3(int x) { f[x][0]=0; f[x][1]=val[x]; for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=fa[x]) { dfs3(y); f[x][0]+=max(f[y][0],f[y][1]); f[x][1]+=f[y][0]; } } g[x][0]=0,g[x][1]=val[x]; for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=fa[x]&&y!=son[x]) { g[x][0]+=max(f[y][0],f[y][1]); g[x][1]+=f[y][0]; } } mat[x].a[0][0]=g[x][0]; mat[x].a[0][1]=g[x][0]; mat[x].a[1][0]=g[x][1]; mat[x].a[1][1]=-INF; } struct segment_tree { struct node { int l; int r; matrix v; } tree[maxn*4+5]; void push_up(int pos) { tree[pos].v=tree[pos<<1].v*tree[pos<<1|1].v; } void build(int l,int r,int pos) { tree[pos].l=l; tree[pos].r=r; if(l==r) { tree[pos].v=mat[hash_dfn[l]]; return; } int mid=(l+r)>>1; build(l,mid,pos<<1); build(mid+1,r,pos<<1|1); push_up(pos); } void update(int upos,matrix &uval,int pos) { if(tree[pos].l==tree[pos].r) { tree[pos].v=uval; return; } int mid=(tree[pos].l+tree[pos].r)>>1; if(upos<=mid) update(upos,uval,pos<<1); else update(upos,uval,pos<<1|1); push_up(pos); } matrix query(int L,int R,int pos) { if(L<=tree[pos].l&&R>=tree[pos].r) return tree[pos].v; int mid=(tree[pos].l+tree[pos].r)>>1; matrix ans; ans.a[0][0]=ans.a[1][1]=0; ans.a[0][1]=ans.a[1][0]=-INF; if(L<=mid) ans=ans*query(L,R,pos<<1); if(R>mid) ans=ans*query(L,R,pos<<1|1); return ans; } } T; ll get_f(int x,int k) { //f[x]需要从x所在重链底端推上来,变成区间矩阵乘法 return T.query(dfn[x],dfn[btm[x]],1).a[k][0]; } void change(int x,int v) { g[x][1]+=v-val[x]; val[x]=v; while(x) { mat[x].a[0][0]=g[x][0]; mat[x].a[0][1]=g[x][0]; mat[x].a[1][0]=g[x][1]; mat[x].a[1][1]=-INF; T.update(dfn[x],mat[x],1); x=top[x]; g[fa[x]][0]-=max(f[x][0],f[x][1]); g[fa[x]][1]-=f[x][0]; f[x][0]=get_f(x,0); f[x][1]=get_f(x,1); g[fa[x]][0]+=max(f[x][0],f[x][1]); g[fa[x]][1]+=f[x][0]; x=fa[x]; } } int main() { int u,v; scanf("%d %d",&n,&m); for(int i=1; i<=n; i++) scanf("%lld",&val[i]); for(int i=1; i<n; i++) { scanf("%d %d",&u,&v); add_edge(u,v); add_edge(v,u); } dfs1(1,0); dfs2(1,1); dfs3(1); T.build(1,n,1); for(int i=1; i<=m; i++) { scanf("%d %d",&u,&v); change(u,v); printf("%lld\n",max(get_f(1,0),get_f(1,1))); } }
LCT:
#include<iostream> #include<cstdio> #include<cstring> #define INF 0x3f3f3f3f #define maxn 200000 using namespace std; typedef long long ll; int n,m; struct edge { int from; int to; int next; } E[maxn*2+5]; int head[maxn+5]; int esz=1; void add_edge(int u,int v) { esz++; E[esz].from=u; E[esz].to=v; E[esz].next=head[u]; head[u]=esz; } struct matrix { ll a[2][2]; matrix(){ a[0][0]=a[0][1]=a[1][0]=a[1][1]=-INF; } inline void set(int x) { for(int i=0; i<2; i++) { for(int j=0; j<2; j++) a[i][j]=x; } } friend matrix operator * (matrix p,matrix q) { matrix ans; ans.set(-INF); for(int i=0; i<2; i++) { for(int j=0; j<2; j++) { for(int k=0; k<2; k++) { ans.a[i][j]=max(ans.a[i][j],p.a[i][k]+q.a[k][j]); } } } return ans; } } mat[maxn+5]; ll val[maxn+5]; ll f[maxn+5][2],g[maxn+5][2]; struct LCT { #define lson(x) (tree[x].ch[0]) #define rson(x) (tree[x].ch[1]) #define fa(x) (tree[x].fa) struct node { int ch[2]; int fa; matrix v; } tree[maxn+5]; inline bool is_root(int x) { //注意合并顺序 return !(lson(fa(x))==x||rson(fa(x))==x); } inline int check(int x) { return rson(fa(x))==x; } void push_up(int x) { tree[x].v=mat[x]; if(lson(x)) tree[x].v=tree[lson(x)].v*tree[x].v; if(rson(x)) tree[x].v=tree[x].v*tree[rson(x)].v; } void rotate(int x) { int y=tree[x].fa,z=tree[y].fa,k=check(x),w=tree[x].ch[k^1]; tree[y].ch[k]=w; tree[w].fa=y; if(!is_root(y)) tree[z].ch[check(y)]=x; tree[x].fa=z; tree[x].ch[k^1]=y; tree[y].fa=x; push_up(y); push_up(x); } void splay(int x) { while(!is_root(x)) { int y=fa(x); if(!is_root(y)) { if(check(x)==check(y)) rotate(y); else rotate(x); } rotate(x); } } void access(int x) { //access的时候可能由实变虚,或由虚变实,因此要更新f,g,方法类似LCT维护虚子树信息 //这里和树剖向上跳重链更新是类似的 if(x==2){ x=2; } for(int y=0; x; y=x,x=fa(x)) { splay(x); //原来的rson(x)由实变虚 if(rson(x)){ mat[x].a[0][0]+=max(tree[rson(x)].v.a[0][0],tree[rson(x)].v.a[1][0]);//这里也可以不用f和g,直接写对应矩阵里的值 mat[x].a[1][0]+=tree[rson(x)].v.a[0][0]; } rson(x)=y; if(rson(x)){ mat[x].a[0][0]-=max(tree[rson(x)].v.a[0][0],tree[rson(x)].v.a[1][0]); mat[x].a[1][0]-=tree[rson(x)].v.a[0][0]; } mat[x].a[0][1]=mat[x].a[0][0]; push_up(x); } } void change(int x,int v) { access(x); splay(x); mat[x].a[1][0]+=v-val[x]; push_up(x); val[x]=v; } ll query(int x) { splay(1);//查询前记得splay到根 return max(tree[1].v.a[0][0],tree[1].v.a[1][0]); } } T; void dfs(int x,int fa) { f[x][0]=0; f[x][1]=val[x]; for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=fa) { dfs(y,x); f[x][0]+=max(f[y][0],f[y][1]); f[x][1]+=f[y][0]; } } mat[x].a[0][0]=mat[x].a[0][1]=f[x][0];//一开始全是轻边,f=g mat[x].a[1][0]=f[x][1]; mat[x].a[1][1]=-INF; T.tree[x].v=mat[x];//初始化LCT T.tree[x].fa=fa; //记得初始化fa } int main() { int u,v; scanf("%d %d",&n,&m); for(int i=1; i<=n; i++) scanf("%lld",&val[i]); for(int i=1; i<n; i++) { scanf("%d %d",&u,&v); add_edge(u,v); add_edge(v,u); } dfs(1,0); for(int i=1; i<=m; i++) { scanf("%d %d",&u,&v); T.change(u,v); printf("%lld\n",T.query(1)); } }
全局平衡二叉树:
#include<iostream> #include<cstdio> #include<cstring> #define INF 0x3f3f3f3f #define maxn 200000 using namespace std; typedef long long ll; int n,m; struct edge { int from; int to; int next; } E[maxn*2+5]; int head[maxn+5]; int esz=1; void add_edge(int u,int v) { esz++; E[esz].from=u; E[esz].to=v; E[esz].next=head[u]; head[u]=esz; } struct matrix { ll a[2][2]; matrix() { a[0][0]=a[0][1]=a[1][0]=a[1][1]=-INF; } inline void set(int x) { for(int i=0; i<2; i++) { for(int j=0; j<2; j++) a[i][j]=x; } } friend matrix operator * (matrix p,matrix q) { matrix ans; ans.set(-INF); for(int i=0; i<2; i++) { for(int j=0; j<2; j++) { for(int k=0; k<2; k++) { ans.a[i][j]=max(ans.a[i][j],p.a[i][k]+q.a[k][j]); } } } return ans; } ll* operator [](int i) { return a[i]; } } mat[maxn+5]; ll val[maxn+5]; ll f[maxn+5][2],g[maxn+5][2]; int sz[maxn+5],lsz[maxn+5],son[maxn+5]; void dfs1(int x,int fa) { sz[x]=lsz[x]=1; f[x][0]=0; f[x][1]=val[x]; for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=fa) { dfs1(y,x); f[x][0]+=max(f[y][0],f[y][1]); f[x][1]+=f[y][0]; sz[x]+=sz[y]; if(sz[son[x]]<sz[y]) son[x]=y; } } g[x][0]=0,g[x][1]=val[x]; for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=fa&&y!=son[x]) { g[x][0]+=max(f[y][0],f[y][1]); g[x][1]+=f[y][0]; lsz[x]+=sz[y]; } } mat[x].a[0][0]=g[x][0]; mat[x].a[0][1]=g[x][0]; mat[x].a[1][0]=g[x][1]; mat[x].a[1][1]=-INF; } struct BST { #define fa(x) (tree[x].fa) #define lson(x) (tree[x].ch[0]) #define rson(x) (tree[x].ch[1]) int root; int tot; int stk[maxn+5];//存储当前重链 int sumsz[maxn+5];//存储重链上点的lsz之和 struct node { int fa;//全局平衡二叉树上的父亲 int ch[2]; matrix v; } tree[maxn+5]; inline bool is_root(int x) { //注意合并顺序 return !(lson(fa(x))==x||rson(fa(x))==x); } void push_up(int x) {//很多函数和LCT是一样的 tree[x].v=mat[x]; if(lson(x)) tree[x].v=tree[lson(x)].v*tree[x].v; if(rson(x)) tree[x].v=tree[x].v*tree[rson(x)].v; } int get_bst(int l,int r) { if(l>r) return 0; int mid=lower_bound(sumsz+l,sumsz+r+1,(sumsz[l-1]+sumsz[r])/2)-sumsz;//求带权重心 int x=stk[mid]; lson(x)=get_bst(l,mid-1); rson(x)=get_bst(mid+1,r);//递归建树,这样的二叉树是平衡的 if(lson(x)) fa(lson(x))=x;//类似LCT,初始化fa和son if(rson(x)) fa(rson(x))=x; push_up(x); return x; } int build(int x,int f) { int rt=0; stk[++tot]=x; sumsz[tot]+=lsz[x]; if(son[x]) { //继续dfs重链 sumsz[tot+1]+=sumsz[tot]; rt=build(son[x],x); } else { //到了重链底部,可以建二叉树了 rt=get_bst(1,tot); for(int i=1; i<=tot; i++) sumsz[i]=0; tot=0; return rt; } for(int i=head[x]; i; i=E[i].next) { int y=E[i].to; if(y!=f&&y!=son[x]) fa(build(y,x))=x;//对于轻链,递归下去建树,再用fa把它们连起来 } return rt; } void update(int x) { while(x) { //这一部分和树剖跳重链类似 int f=fa(x); if(f&&is_root(x)) {//只有到了BST根的时候,说明已经处理完了整条重链,跳轻链到fa(x)更新上一条重链 mat[f][0][0]-=max(tree[x].v[0][0],tree[x].v[1][0]); mat[f][0][1]-=max(tree[x].v[0][0],tree[x].v[1][0]); mat[f][1][0]-=tree[x].v[0][0]; } push_up(x); if(f&&is_root(x)) { mat[f][0][0]+=max(tree[x].v[0][0],tree[x].v[1][0]); mat[f][0][1]+=max(tree[x].v[0][0],tree[x].v[1][0]); mat[f][1][0]+=tree[x].v[0][0]; } x=fa(x); } } void ini(){ dfs1(1,0); root=build(1,0); } void change(int x,int v) { mat[x][1][0]+=v-val[x]; val[x]=v; update(x); } ll query(){ return max(tree[root].v[0][0],tree[root].v[1][0]); } void debug(){ printf("root=%d\n",root); for(int i=1;i<=n;i++) printf("%d ",fa(i)); printf("\n"); } } T; int main() { int u,v; scanf("%d %d",&n,&m); for(int i=1; i<=n; i++) scanf("%lld",&val[i]); for(int i=1; i<n; i++) { scanf("%d %d",&u,&v); add_edge(u,v); add_edge(v,u); } T.ini(); // T.debug(); for(int i=1; i<=m; i++) { scanf("%d %d",&u,&v); T.change(u,v); printf("%lld\n",T.query()); } }
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 从零开始开发一个 MCP Server!
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Ai满嘴顺口溜,想考研?浪费我几个小时
· Browser-use 详细介绍&使用文档