[LuoguP4719][模板]动态DP(动态DP)
[LuoguP4719][模板]动态DP(动态DP)
题面
给出一棵\(n\)个点的树,点带权。\(m\)组修改,每次修改一个点的点权,并询问整棵树最大权独立集大小。
分析
约定:\(child(x)\)表示\(x\)的儿子集合,\(son(x)\)表示\(x\)的重儿子。
先写出树形DP.设\(f_{x,0/1}\)表示不选或选\(x\),\(x\)的子树里最大权独立集的大小.
如果不选\(x\),那么儿子\(y\)可以任意选
\[f_{x,0}=\sum_{y \in child(x)} \max(f_{y,0},f_{y,1})
\]
如果选了\(x\),那么儿子\(y\)就不能选。
\[f_{x,1}=val_x+\sum_{y \in child(x)} f_{y,0}
\]
动态DP的套路,轻链和重链分别维护,令:
\[g_{x,0}=\sum_{y \in child(x)-\{son(x)\}}\max(f_{y,0},f_{y,1})
\]
\[g_{x,1}=val_x+\sum_{y \in child(x)-\{son(x)\}}f_{y,0}
\]
那么有:
\(f_{x,0}=\max(f_{son(x),0},f_{son(x),1})+g_{x,0}\)
\(f_{x,1}=f_{son(x),0}+g_{x,1}\)
写成矩阵的形式(注意这里是max,+矩阵乘法)
\[\begin{bmatrix}f_{x,0} \\ f_{x,1} \end{bmatrix}=\begin{bmatrix}g_{x,0} \ g_{x,0} \\ g_{x,1} \ -\infty \end{bmatrix} \begin{bmatrix}f_{son(x),0} \\ f_{son(x),1} \end{bmatrix}
\]
然后按照动态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());
}
}
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢