iinattr

万字长文·动态树分治/动态动态规划/ddp入门经典

2022-07-24

万字长文·动态树分治/动态动态规划/ddp入门经典

这东西好像有人叫它ddp

其实应该是动态树上dp吧

这类题往往是一个画风正常的树上dp问题加上修改

并且修改次数极多

注意到树上dp往往一个点权的修改只会更改它到根的路径上的答案

所以我们可以每次在根的路径上求解

可通过一些弱数据

但复杂度不正确

考虑树链剖分

一条链上的转移可以写成矩阵快速幂

不过我们需要重新定义卷积


一道例题

P4719

设$f_{n,0/1}$表示以i为根的子树选不选根的最大值

发现dp方程

然后设$g_{i,0/1}$ 为不考虑重儿子 选不选根的最大值

方程重写作

这样就规避了和式 并且根据树剖 只会修改重儿子 所以具有相当的稳定性

之后写出矩阵 易得

于是这样满足了链尾为叶子的性质

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include<bits/stdc++.h>
using namespace std;
#define N 200005
#define INF 0x3f3f3f3f
int n,m,w[N];
vector<int> mp[N];
struct matrix
{
int a[3][3],n,m;
void init(int r,int c)
{
n=r;
m=c;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
a[i][j]=-INF;
}
matrix(int r=0,int c=0)
{
init(r,c);
}
matrix operator * (const matrix &k1)const
{
matrix ret(n,k1.m);
for(int k=0;k<k1.n;k++)
for(int i=0;i<n;i++)
for(int j=0;j<k1.m;j++)
ret.a[i][j]=
max(ret.a[i][j],a[i][k]+k1.a[k][j]);
return ret;
}
}val[N];
struct segtree
{
struct node
{
matrix mat;
int l,r;
}tr[N<<2];
void build(int x,int l,int r)
{
tr[x].l=l;tr[x].r=r;
if(l==r)
{
tr[x].mat=val[l];
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid),build(x<<1|1,mid+1,r);
tr[x].mat=tr[x<<1].mat*tr[x<<1|1].mat;
}
void change(int x,int p)
{
if(tr[x].l==p&&tr[x].r==p)
{
tr[x].mat=val[p];
return;
}
int mid=(tr[x].l+tr[x].r)>>1;
if(p<=mid) change(x<<1,p);
if(p>mid) change(x<<1|1,p);
tr[x].mat=tr[x<<1].mat*tr[x<<1|1].mat;
}
matrix query(int x,int l,int r)
{
if(l<=tr[x].l&&tr[x].r<=r)
return tr[x].mat;
int mid=(tr[x].l+tr[x].r)>>1;
if(r<=mid) return query(x<<1,l,r);
else if(l>mid) return query(x<<1|1,l,r);
else return query(x<<1,l,r)*query(x<<1|1,l,r);
}
}tre;
int idx=0,id[N],ed[N],dep[N],fa[N],son[N],top[N],size[N];
void dfs1(int x,int f,int deep)
{
dep[x]=deep;size[x]=1;fa[x]=f,size[son[x]=0]=-1;
for(int i=0;i<mp[x].size();i++)
{
int u=mp[x][i];
if(u==f) continue;
dfs1(u,x,deep+1);
size[x]+=size[u];
son[x]=size[u]>size[son[x]]?u:son[x];
}
}
int f[N][2],g[N][2];
void dfs2(int x,int topfa)
{
top[x]=topfa;ed[topfa]=x;id[x]=++idx;
g[x][0]=0,g[x][1]=w[x];
if(!son[x])
{
f[x][0]=0;f[x][1]=w[x];
val[id[x]].init(2,1);
val[id[x]].a[0][0]=f[x][0];
val[id[x]].a[1][0]=f[x][1];
return;
}
dfs2(son[x],topfa);
for(int i=0;i<mp[x].size();i++)
{
int v=mp[x][i];
if(v!=fa[x]&&v!=son[x])
dfs2(v,v),g[x][0]+=max(f[v][0],f[v][1]),
g[x][1]+=f[v][0];
}
f[x][0]=g[x][0]+max(f[son[x]][0],f[son[x]][1]);
f[x][1]=g[x][1]+f[son[x]][0];
val[id[x]].init(2,2);
val[id[x]].a[0][0]=val[id[x]].a[0][1]=g[x][0];
val[id[x]].a[1][0]=g[x][1];
}
void change(int x,int v)
{
val[id[x]].a[1][0]+=v-w[x];
int bp=x;
while(x)
{
matrix lst=tre.query(1,id[top[x]],id[ed[top[x]]]);
tre.change(1,id[x]);
matrix now=tre.query(1,id[top[x]],id[ed[top[x]]]);
x=fa[top[x]];
val[id[x]].a[0][0]+=max(now.a[0][0],now.a[1][0])-max(lst.a[0][0],lst.a[1][0]);
val[id[x]].a[0][1]=val[id[x]].a[0][0];
val[id[x]].a[1][0]+=now.a[0][0]-lst.a[0][0];
}
w[bp]=v;
}
int query(int x)
{
matrix ans=tre.query(1,id[x],id[ed[top[x]]]);
return max(ans.a[0][0],ans.a[1][0]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i];
for(int i=1,x,y;i<=n-1;i++)
{
cin>>x>>y;
mp[x].push_back(y);
mp[y].push_back(x);
}
dfs1(1,0,1);
dfs2(1,1);
tre.build(1,1,n);
for(int i=1,x,y;i<=m;i++)
{
cin>>x>>y;
change(x,y);
cout<<query(1)<<endl;
}
return 0;
}