动态 dp 学习笔记
Wy_x
·
·
算法·理论
Update:2025/10/28
感谢 Aurie 指出本文的两处错误。已经修改。
致歉:作者是在打代码之前就完成了文字部分,转移方程的锅代码中修了,文字部分没修,在此致歉。
【模板】动态 DP 加强版 题解
该篇为题解。
总文章(动态 dp 学习笔记)同步发表于 cnblogs。
总文章(动态 dp 学习笔记)同步发表于 luogu。
前置知识:
P4719 【模板】动态 DP
本文着重讲下修改的具体过程以及代码实现,蒟蒻花了好长时间才明白。
鏖战一天终于通过了板子题啊啊啊!!!
不带修:简单树上 dp。
考虑不带修改,就是一个平凡的树上最大权独立集问题,简单树上 dp 即可求解。
设 dp[i][0/1] 表示以 i 为根子树中选/不选 i 所得到的树上最大权独立集的大小。
转移是容易的:
\begin{equation*}
\begin{aligned}
&dp_{i,0}=\sum \max(dp_{son,1},dp_{son,0})\\
&dp_{i,1}=\sum dp_{son,0}\\
\end{aligned}
\end{equation*}
带修了!
但是丧心病狂的出题人加上了修改点权!
考虑动态维护这个问题。
发现树剖有一个性质:跳重链至多 O(\log) 次。
设:
$top_p$ 为 $p$ 点所在重链的链头节点。
$fa_p$ 为 $p$ 的父亲节点。
然后修正我们的 dp 为:
$f[p][0/1]$ 表示以 $p$ 为根子树选$/$不选 $p$ 的答案。
$g[p][0/1]$ 表示以 $p$ 为根子树**不选以重儿子为根子树中(包括重儿子)任何一个点时**,选$/$不选 $p$ 的答案。
设 $to$ 为点 $p$ 的儿子节点,那么有转移:
$$
\begin{equation*}
\begin{aligned}
&g_{p,0}=\sum \max(f_{to,0},f_{to,1}) [ to \ne son_p ] \\
&g_{p,1}=val_p+ \sum f_{to,0} [ to \ne son_p ] \\
&f_{p,0}=g_{p,0}+\max(f_{son_p,0},f_{son_p,1})\\
&f_{p,1}=g_{p,1}+f_{son_p,0}\\
\end{aligned}
\end{equation*}$$
------------------------
### 转移改为使用广义矩阵乘法:
直接定义广义矩阵乘法 $C_{i,j}=\max_k(A_{i,k}+B_{k,j})$。
本文并不在广义矩阵乘这里深入展开,具体可参考其他博客。作者还没完全搞懂。
具体可参考 [https://www.cnblogs.com/qkhm/p/19055513/ddp](https://www.cnblogs.com/qkhm/p/19055513/ddp)。
当然如果你不会证明你也可以直接设三个矩阵然后暴力手算验证该新定义的矩阵是否满足结合律,也是可行的。
发现这个新的矩乘还是满足结合律(不满足交换律),单位矩阵是主对角线上是 $0$,其他全都是 $−\infty$。
本题单位矩阵为
$$
I=
\begin{bmatrix}
0 & -\infty \\
-\infty & 0
\end{bmatrix}
$$
设**原树上**点 $p$ 的答案矩阵 $B_p$ 为
$$
B_p=
\begin{bmatrix}
f_{p,0}\\
f_{p,1}\\
\end{bmatrix}
$$
那么我们需要求出每个点的转移矩阵,设为 $A_p$。
定义完所有所需矩阵之后,转移可以写为:
$$
A_p
\times
B_{son_p}
= B_p
$$
设
$$
A_p=\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}
$$
写出完整矩阵转移的柿子为
$$
\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}
\times
\begin{bmatrix}
f_{son_p,0} \\
f_{son_p,1}\\
\end{bmatrix}
= \begin{bmatrix}
f_{p,0}\\
f_{p,1}\\
\end{bmatrix}
$$
由待定系数法
$$
\begin{equation*}
\begin{aligned}
f_{p,0}&=\max(a + f_{son_p,0}, b+f_{son_p,1} )\\
&=g_{p,0}+\max(f_{son_p,0},f_{son_p,1})\\
&=\max({\color{red}g_{p,0}}+f_{son_p,0},{\color{red}g_{p,0}}+f_{son_p,1})\\
\end{aligned}
\end{equation*}$$
$$
\begin{equation*}
\begin{aligned}
f_{p,1}&=\max(c+f_{son_p,0}, d+f_{son_p,1})\\
&=g_{p,1}+f_{son_p,0}\\
&=\max({\color{red}g_{p,1}}+f_{son_p,0},{\color{red}-\infty} +f_{son_p,1} )\\
\end{aligned}
\end{equation*}$$
得出 $p$ 点的转移矩阵为
$$
A_p=
\begin{bmatrix}
g_{p,0} & g_{p,0} \\
g_{p,1} & -\infty
\end{bmatrix}
$$
那么转移可以写成
$$
A_p
\times
\begin{bmatrix}
f_{son_p,0} \\
f_{son_p,1}\\
\end{bmatrix}
= \begin{bmatrix}
f_{p,0}\\
f_{p,1}\\
\end{bmatrix}
$$
那么一条重链上某一点 $p$ 的答案矩阵($f$ 值)可以用矩阵连乘计算:
$$
B_p=A_p \times A_{son_p} \times A_{son_{son_p}}\times \dots \times
\begin{bmatrix}
0\\
0\\
\end{bmatrix}
$$
链尾节点没有 $son$,所以 $B_{son_{tail}} =
\left[
\begin{smallmatrix}
0\\
0\\
\end{smallmatrix}
\right]
$。
手算发现乘
\left[
\begin{smallmatrix}
0\
0\
\end{smallmatrix}
\right]
\left[
\begin{smallmatrix}
0\
0\
\end{smallmatrix}
\right]
矩阵,直接提取第一列即可。
--------------------
### 初始化:
那么我们就可以在线段树上维护区间矩阵连乘积了。注意矩阵乘法不满足交换律,我的做法在合并区间时需要左 $\times$ 右。
具体实现时比较复杂。
先两次 dfs 进行树链剖分。我们需要额外记录一个 $bot_i$ 表示一条以 $i$ 为链首的那条重链的链底。
再来一次 dfs 跑一遍树上 dp 初始化 $f$ 和 $g$ 数组。
在 dfn 序上建线段树,每个叶子节点记录它代表的树上点 $p$ 的转移矩阵 $A_p$。
对于非叶子节点,记录的矩阵为左儿子矩阵乘右儿子矩阵。
查询答案时我们需要查询以根为链首的 $B_{top}$ 矩阵值,可以通过线段树区间查询该重链的矩阵乘积得到。
------------------------------
### 解决修改问题:
**着重讲下修改的具体过程以及代码实现,蒟蒻花了好长时间才明白。**
注意,下文对 $g$ 的修改**改的是矩阵里的值**,而不是 $g$ 数组的值。
设我们要将树上点 $p$ 的权值改为 $k$。设原先点权为 $val_p$。
首先**开一个全局临时矩阵** $X$,令 $X = A_p$。
然后修改矩阵 $X$ 中 $g_{p,1}$ 的值,也就是矩阵的第二行第一列那个位置的值。容易发现点 $p$ 对 $g_{p,1}$ 的贡献由原先的 $val_p$ 变为 $k$,变化量为 $k-val_p$,所以我们在矩阵中更改 $g_{p,1}=g_{p,1}+k-val_p$ 即可。
然后我们考察 $g$ 的实际意义,为一个点轻儿子的贡献。考虑有几个点的 $g$ 值需要更新。发现是 $p$ 点和跳链过程中每条链链顶的父亲,其他点均不需要修改。
由于每次查询根链最多跳 $\log n$ 条重链,所以对应的转移矩阵 $A_p$ 只会有 $\log n$ 个得到修改,加上线段树的复杂度就是 $\log^2$ 的。复杂度得到证明。
然后现在节点为 $p$,开始跳链操作。
先线段树区间查询 $p$ 所在重链的乘积为矩阵 $Pre$,$Pre$ 的第一列即为**修改之前**链顶的答案矩阵的值($f$ 值)。
然后线段树单点修改 $p$ 点的转移矩阵,将其变为 $X$。
再线段树区间查询 $p$ 所在重链的乘积为矩阵 $Nxt$,$Nxt$ 的第一列即为**修改之后**链顶的答案矩阵的值($f$ 值)。
设 $to = top_p$,然后令 $p = fa_{top_p}$,意为令 $p$ 跳到 $p$ 所在重链链首的父亲。
再次线段树单点查询出 $p$ 点所对应的转移矩阵 $A_p$,令 $X=A_p$。
考察 $to$ 对 $g_{p,0}$ 的贡献,为 $\max(f_{to,0},f_{to,1})$。
那么矩阵 $X$ 里的所有 $g_{p,0}$ 变化量即为 $\max(Nxt_{1,1},Nxt_{2,1}) - \max(Pre_{1,1},Pre_{2,1})$。将 $X_{1,1}$ 和 $X_{1,2}$ 加上变化量即可。
考察 $to$ 对 $g_{p,1}$ 的贡献,为 $f_{to,0}$。
那么矩阵 $X$ 里的所有 $g_{p,1}$ 变化量即为 $Nxt_{1,1} - Pre_{1,1}$。将 $X_{2,1}$ 加上变化量即可。
矩阵 $X$ 中 $X_{2,2}$ 仍为 $-\infty$。
重复执行上述过程直至 $p = 0$ 时结束。
即可完成修改。
------------------------------
### 实现细节:
- 矩阵可以用 $2 \times 2$ 的二维数组存储。
- 矩阵乘可以循环展开。
- 线段树上非叶子节点存储的矩阵为左儿子矩阵 $\times$ 右儿子矩阵。
-------------------------
### Code:
```cpp
#include<bits/stdc++.h>
#define int long long
#define lp (p<<1)
#define rp ((p<<1)|1)
using namespace std;
inline int read()
{
int x=0,c=getchar(),f=0;
for(;c>'9'||c<'0';f=c=='-',c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
return f?-x:x;
}
inline void write(int x)
{
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar(x%10+'0');
}
#ifndef ONLINE_JUDGE
#define ONLINE_JUDGE
#endif
const int N=1e6+5;
int n,m;
int a[N];
int fa[N];
int tail[N];
int siz[N];
int son[N];
int dfn[N];
int tod[N];
int top[N];
int id[N];
int tot;
vector<int> E[N];
const int inf=1e12;
struct Martix
{
int a[2][2]={};
}I,t[N<<2];
Martix nw;
int f[N][2],g[N][2];
int tid[N<<2];
inline int max(int x,int y) { return x>y?x:y; }
Martix operator*(const Martix &x,const Martix &y)
{
Martix ans;
ans.a[0][0]=max(x.a[0][0]+y.a[0][0],x.a[0][1]+y.a[1][0]);
ans.a[0][1]=max(x.a[0][0]+y.a[0][1],x.a[0][1]+y.a[1][1]);
ans.a[1][0]=max(x.a[1][0]+y.a[0][0],x.a[1][1]+y.a[1][0]);
ans.a[1][1]=max(x.a[1][0]+y.a[0][1],x.a[1][1]+y.a[1][1]);
return ans;
}
Martix ksm(Martix x,int p)
{
Martix ans=I;
while(p)
{
if(p&1) ans=ans*x;
x=x*x;
p>>=1;
}
return ans;
}
void dfs1(int p,int f)
{
fa[p]=f;
siz[p]=1;
for(int to:E[p])
{
if(to==f) continue;
dfs1(to,p);
siz[p]+=siz[to];
if(siz[to]>siz[son[p]]) son[p]=to;
}
}
void dfs2(int p,int tp)
{
dfn[p]=++tot;
id[tot]=p;
top[p]=tp;
tail[tp]=p;
if(son[p]) dfs2(son[p],tp);
for(int to:E[p])
if(!dfn[to]) dfs2(to,to);
}
void dfs3(int p,int fa)
{
g[p][1]=a[p];
for(int to:E[p])
{
if(to==fa) continue;
dfs3(to,p);
if(to==son[p]) continue;
g[p][1]+=f[to][0];
g[p][0]+=max(f[to][0],f[to][1]);
}
f[p][0]=g[p][0]+max(f[son[p]][0],f[son[p]][1]);
f[p][1]=g[p][1]+f[son[p]][0];
}
void pushup(int p)
{
t[p]=t[lp]*t[rp];
}
void build(int l,int r,int p)
{
if(l==r)
{
tid[id[l]]=p;
t[p].a[0][0]=t[p].a[0][1]=g[id[l]][0];
t[p].a[1][0]=g[id[l]][1];
t[p].a[1][1]=-inf;
return;
}
int mid=(l+r)>>1;
build(l,mid,lp);
build(mid+1,r,rp);
pushup(p);
}
Martix query(int l,int r,int sl,int sr,int p)
{
if(p==0) exit(0);
if(sl<=l&&r<=sr) return t[p];
int mid=(l+r)>>1;
Martix ql=I,qr=I;
if(sl<=mid) ql=query(l,mid,sl,sr,lp);
if(sr>mid) qr=query(mid+1,r,sl,sr,rp);
return ql*qr;
}
void change(int l,int r,int x,int p,const Martix &nw)
{
if(l==r)
{
t[p]=nw;
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,x,lp,nw);
else change(mid+1,r,x,rp,nw);
pushup(p);
}
void change(int x,int y)
{
nw=t[tid[x]];
nw.a[1][0]+=y-a[x];
a[x]=y;
while(x)
{
Martix last=query(1,n,dfn[top[x]],dfn[tail[top[x]]],1);
change(1,n,dfn[x],1,nw);
Martix cur=query(1,n,dfn[top[x]],dfn[tail[top[x]]],1);
x=fa[top[x]];
nw=t[tid[x]];
nw.a[0][0]+=max(cur.a[0][0],cur.a[1][0])-max(last.a[0][0],last.a[1][0]);
nw.a[0][1]+=max(cur.a[0][0],cur.a[1][0])-max(last.a[0][0],last.a[1][0]);
nw.a[1][0]+=cur.a[0][0]-last.a[0][0];
}
}
signed main()
{
I.a[0][0]=I.a[1][1]=0;
I.a[0][1]=I.a[1][0]=-inf;
t[0]=I;
n=read();
m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
E[u].push_back(v);
E[v].push_back(u);
}
dfs1(1,0);
memset(siz,0,sizeof(siz));
dfs2(1,1);
dfs3(1,0);
build(1,n,1);
while(m--)
{
int x=read(),y=read();
change(x,y);
Martix ans=query(1,n,1,dfn[tail[1]],1);
write(max({ans.a[0][0],ans.a[1][0],ans.a[1][1],ans.a[0][1]}));
putchar('\n');
}
return 0;
}
```
## [P4751 【模板】动态 DP(加强版)](https://www.luogu.com.cn/problem/P4751)
我册出题人竟然卡树剖!
干了哥们儿!
### 卡常技巧:
- 矩阵可以用 $2 \times 2$ 的二维数组存储。
- 矩阵乘可以循环展开。
- `pushup` 写 `#define`。
- 不开 `long long`。
- 对每条重链单开线段树维护,优化掉线段树区间查求上文的 $Pre$ 和 $Nxt$ 矩阵的过程,改为直接访问该重链根节点记录的矩阵(重要优化)。
- 使用 `_unlocked` 以面对大量读入,手写 `buf` 使用 `fread` 也可(重要优化)。
- 你的线段树需要动态开点,精细实现,每遇到一个非叶子节点直接开它的左右儿子,以令内存访问极为连续(重要优化)。
- 手写 $\max$。
- 再夜深人静时或大早上交,卡评测机波动。
- 使用 $\texttt{C++17/C++98/C++23}$ 提交。
### Code:
```cpp
#include<bits/stdc++.h>
// #define int long long
using namespace std;
inline int read()
{
int x=0,c=getchar_unlocked(),f=0;
for(;c>'9'||c<'0';f=c=='-',c=getchar_unlocked());
for(;c>='0'&&c<='9';c=getchar_unlocked())
x=(x<<1)+(x<<3)+(c^48);
return f?-x:x;
}
inline void write(int x)
{
if(x<0) x=-x,putchar_unlocked('-');
if(x>9) write(x/10);
putchar_unlocked(x%10+'0');
}
const int N=1e6+5;
int n,m;
int a[N];
int fa[N];
int tail[N];
int siz[N];
int son[N];
int dfn[N];
int tod[N];
int top[N];
int id[N];
int tot;
vector<int> E[N];
const int inf=1e9;
struct Martix{
int a[2][2]={};
}I;
struct Tree
{
int lp,rp;
Martix a;
}t[N<<2];
int cntroot=0;
int root[N];
int f[N][2],g[N][2];
int tid[N];
Martix nw;
inline int max(int x,int y) { return x>y?x:y; }
Martix operator*(const Martix &x,const Martix &y)
{
Martix ans;
ans.a[0][0]=max(x.a[0][0]+y.a[0][0],x.a[0][1]+y.a[1][0]);
ans.a[0][1]=max(x.a[0][0]+y.a[0][1],x.a[0][1]+y.a[1][1]);
ans.a[1][0]=max(x.a[1][0]+y.a[0][0],x.a[1][1]+y.a[1][0]);
ans.a[1][1]=max(x.a[1][0]+y.a[0][1],x.a[1][1]+y.a[1][1]);
return ans;
}
Martix ksm(Martix x,int p)
{
Martix ans=I;
while(p)
{
if(p&1) ans=ans*x;
x=x*x;
p>>=1;
}
return ans;
}
void dfs1(int p,int f)
{
fa[p]=f;
siz[p]=1;
for(int to:E[p])
{
if(to==f) continue;
dfs1(to,p);
siz[p]+=siz[to];
if(siz[to]>siz[son[p]]) son[p]=to;
}
}
void dfs2(int p,int tp)
{
siz[tp]++;
dfn[p]=++tot;
id[tot]=p;
top[p]=tp;
tail[tp]=p;
if(son[p]) dfs2(son[p],tp);
else root[tp]=++cntroot;
for(int to:E[p])
if(!dfn[to]) dfs2(to,to);
}
void dfs3(int p,int fa)
{
g[p][1]=a[p];
for(int to:E[p])
{
if(to==fa) continue;
dfs3(to,p);
if(to==son[p]) continue;
g[p][1]+=f[to][0];
g[p][0]+=max(f[to][0],f[to][1]);
}
f[p][0]=g[p][0]+max(f[son[p]][0],f[son[p]][1]);
f[p][1]=g[p][1]+f[son[p]][0];
}
void build(int rt,int l,int r,int &p)
{
if(l==r)
{
tid[id[rt+l-1]]=p;
t[p].a.a[0][0]=t[p].a.a[0][1]=g[id[rt+l-1]][0];
t[p].a.a[1][0]=g[id[rt+l-1]][1];
t[p].a.a[1][1]=-inf;
return;
}
if(!t[p].lp) t[p].lp=++tot;
if(!t[p].rp) t[p].rp=++tot;
int mid=(l+r)>>1;
build(rt,l,mid,t[p].lp);
build(rt,mid+1,r,t[p].rp);
t[p].a=t[t[p].lp].a*t[t[p].rp].a;
}
void change(int l,int r,int x,int p,const Martix &nw)
{
if(l==r)
{
t[p].a=nw;
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,x,t[p].lp,nw);
else change(mid+1,r,x,t[p].rp,nw);
t[p].a=t[t[p].lp].a*t[t[p].rp].a;
}
void change(int x,int y)
{
nw=t[tid[x]].a;
nw.a[1][0]+=y-a[x];
a[x]=y;
while(x)
{
Martix last=t[root[top[x]]].a;
change(1,siz[top[x]],dfn[x]-dfn[top[x]]+1,root[top[x]],nw);
Martix cur=t[root[top[x]]].a;
x=fa[top[x]];
nw=t[tid[x]].a;
nw.a[0][0]+=max(cur.a[0][0],cur.a[1][0])-max(last.a[0][0],last.a[1][0]);
nw.a[0][1]+=max(cur.a[0][0],cur.a[1][0])-max(last.a[0][0],last.a[1][0]);
nw.a[1][0]+=cur.a[0][0]-last.a[0][0];
}
}
signed main()
{
I.a[0][0]=I.a[1][1]=0;
I.a[0][1]=I.a[1][0]=-inf;
t[0].a=I;
n=read();
m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
E[u].push_back(v);
E[v].push_back(u);
}
dfs1(1,0);
memset(siz,0,sizeof(siz));
dfs2(1,1);
tot=0;
dfs3(1,0);
tot=cntroot;
for(int i=1;i<=n;i++) if(i==top[i]) build(dfn[i],1,siz[i],root[i]);
int lastans=0;
Martix ans;
while(m--)
{
int x=read()^lastans,y=read();
change(x,y);
ans=t[root[top[1]]].a;
lastans=max(ans.a[0][0],ans.a[1][0]);
write(lastans);
putchar_unlocked('\n');
}
return 0;
}
```
## 现学现用:ABC429F
发现是 dp,带修。
直接拍上动态 dp 即可。
设 $f_{i,1/2/3}$ 表示到第 $i$ 列第 $1/2/3$ 行的最短距离。
转移是显然的,就不说了。
我们设答案矩阵为 $B_i$,转移矩阵为 $A_i$。
由于正向,可以设:
$$B_i=
\begin{bmatrix}
f_1 & f_2 & f_3\\
\end{bmatrix}
$$
那么转移矩阵即为:
$$A_i=
\begin{bmatrix}
a & b & c\\
d & e & f\\
x & y & z\\
\end{bmatrix}
$$
直接仿照上文定义广义矩阵乘法:
$C_{i,j}=\min_k(A_{i,k}+B_{k,j})
发现性质不变。
那么有矩阵形式转移:
A_{i-1} \times B_i = A_i
意义即为从第 i-1 列直接向右走一步,再在第 i 列上下移动,到这一列每一行的距离最小值。
由实际意义,直接使用待定系数法,得到转移矩阵为:
\begin{bmatrix}
a=1 & b=2 & c=3\\
d=2 & e=1 & f=2\\
x=3 & y=2 & z=1\\
\end{bmatrix}
但这是无障碍情况。
考虑(以下情况均需考虑):
第 2 行第 i 列为障碍:
那么只可能是行 1 \to 1 和 3 \to 3 存在。令 a=z=1,其余为 \infty 即可。
第 1 行第 i 列为障碍:
那么 1 不能到下一列,2 和 3 不能走到 1。令 a=b=c=d=x=\infty。
第 3 行第 i 列为障碍:
那么 3 不能到下一列,1 和 2 不能走到 3。类似的,令 c=f=x=y=z=\infty。
转移矩阵构造完了。
那么考虑:
答案矩阵 B_n 怎么求?
B_n=B_0 \times A_1 \times A_2 \times \dots \times A_n
其中 B_0 中的元素均为 0。
然后直接拍上线段树维护区间矩阵连乘积,修改时直接暴力按照规则重构矩阵,非叶子节点矩阵为左 右。
常数较大。可以通过本题。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
int x=0,c=getchar(),f=0;
for(;c>'9'||c<'0';f=c=='-',c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
return f?-x:x;
}
inline void write(int x)
{
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar(x%10+'0');
}
// #ifndef ONLINE_JUDGE
// #define ONLINE_JUDGE
// #endif
const int inf=1e18;
const int N=2e5+5;
int n;
char c[N][4];
struct Martix{
int a[3][3]={};
}t[N<<2],I,I2;
Martix operator*(const Martix &x,const Martix &y)
{
Martix ans;
memset(ans.a,0x3f,sizeof(ans.a));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
for(int k=0;k<3;k++)
ans.a[i][j]=min(ans.a[i][j],x.a[i][k]+y.a[k][j]);
return ans;
}
#define lp (p<<1)
#define rp ((p<<1)|1)
Martix Make(int i)
{
Martix ans;
ans=I;
if(c[i][2]=='#')
{
ans=I2;
ans.a[0][0]=ans.a[2][2]=1;
}
if(c[i][1]=='#')
{
ans.a[0][0]=ans.a[0][1]=ans.a[0][2]=inf;
ans.a[1][0]=ans.a[2][0]=inf;
}
if(c[i][3]=='#')
{
ans.a[2][0]=ans.a[2][1]=ans.a[2][2]=inf;
ans.a[0][2]=ans.a[1][2]=inf;
}
return ans;
}
void build(int l,int r,int p)
{
if(l==r)
{
t[p]=Make(l);
return;
}
int mid=(l+r)>>1;
build(l,mid,lp);
build(mid+1,r,rp);
t[p]=t[lp]*t[rp];
// cout<<"["<<l<<","<<r<<"] p=\n";
// for(int i=0;i<3;i++,cout<<"\n")
// for(int j=0;j<3;j++)
// cout<<t[p].a[i][j]<<" ";
// cout<<"\n\n";
}
void change(int l,int r,int x,int p)
{
if(l==r)
{
t[p]=Make(l);
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,x,lp);
else change(mid+1,r,x,rp);
t[p]=t[lp]*t[rp];
// cout<<"["<<l<<","<<r<<"] p=\n";
// for(int i=0;i<3;i++,cout<<"\n")
// for(int j=0;j<3;j++)
// cout<<t[p].a[i][j]<<" ";
// cout<<"\n\n";
}
signed main()
{
I.a[0][0]=1;
I.a[0][1]=2;
I.a[0][2]=3;
I.a[1][0]=2;
I.a[1][1]=1;
I.a[1][2]=2;
I.a[2][0]=3;
I.a[2][1]=2;
I.a[2][2]=1;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
I2.a[i][j]=inf;
cin>>n;
for(int j=1;j<=3;j++)
for(int i=1;i<=n;i++) cin>>c[i][j];
int Q=0;
cin>>Q;
build(1,n,1);
// Martix nw=t[1];
// int ans=nw.a[0][2];
// if(ans>=1e8) ans=-1;
// write(ans);
// putchar('\n');
while(Q--)
{
int x,y;
cin>>y>>x;
// cout<<x<<" "<<y<<"\n";
if(c[x][y]=='#') c[x][y]='.';
else c[x][y]='#';
change(1,n,x,1);
Martix nw=t[1];
int ans=nw.a[0][2]-1;
if(ans>=1e8) ans=-1;
write(ans);
putchar('\n');
// cout<<"\n-------------------\n\n";
}
return 0;
}
现学现用:ABC429F
发现是 dp,带修。
直接拍上动态 dp 即可。
设 f_{i,1/2/3} 表示到第 i 列第 1/2/3 行的最短距离。
转移是显然的,就不说了。
我们设答案矩阵为 B_i,转移矩阵为 A_i。
由于正向,可以设:
\begin{bmatrix}
f_1 & f_2 & f_3\\
\end{bmatrix}
那么转移矩阵即为:
\begin{bmatrix}
a & b & c\\
d & e & f\\
x & y & z\\
\end{bmatrix}
直接仿照上文定义广义矩阵乘法:
C_{i,j}=\min_k(A_{i,k}+B_{k,j})
发现性质不变。
那么有矩阵形式转移:
A_{i-1} \times B_i = A_i
意义即为从第 i-1 列直接向右走一步,再在第 i 列上下移动,到这一列每一行的距离最小值。
由实际意义,直接使用待定系数法,得到转移矩阵为:
\begin{bmatrix}
a=1 & b=2 & c=3\\
d=2 & e=1 & f=2\\
x=3 & y=2 & z=1\\
\end{bmatrix}
但这是无障碍情况。
考虑(以下情况均需考虑):
第 2 行第 i 列为障碍:
那么只可能是行 1 \to 1 和 3 \to 3 存在。令 a=z=1,其余为 \infty 即可。
第 1 行第 i 列为障碍:
那么 1 不能到下一列,2 和 3 不能走到 1。令 a=b=c=d=x=\infty。
第 3 行第 i 列为障碍:
那么 3 不能到下一列,1 和 2 不能走到 3。类似的,令 c=f=x=y=z=\infty。
转移矩阵构造完了。
那么考虑:
答案矩阵 B_n 怎么求?
B_n=B_0 \times A_1 \times A_2 \times \dots \times A_n
其中 B_0 中的元素均为 0。
然后直接拍上线段树维护区间矩阵连乘积,修改时直接暴力按照规则重构矩阵,非叶子节点矩阵为左 右。
常数较大。可以通过本题。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
int x=0,c=getchar(),f=0;
for(;c>'9'||c<'0';f=c=='-',c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
return f?-x:x;
}
inline void write(int x)
{
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar(x%10+'0');
}
// #ifndef ONLINE_JUDGE
// #define ONLINE_JUDGE
// #endif
const int inf=1e18;
const int N=2e5+5;
int n;
char c[N][4];
struct Martix{
int a[3][3]={};
}t[N<<2],I,I2;
Martix operator*(const Martix &x,const Martix &y)
{
Martix ans;
memset(ans.a,0x3f,sizeof(ans.a));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
for(int k=0;k<3;k++)
ans.a[i][j]=min(ans.a[i][j],x.a[i][k]+y.a[k][j]);
return ans;
}
#define lp (p<<1)
#define rp ((p<<1)|1)
Martix Make(int i)
{
Martix ans;
ans=I;
if(c[i][2]=='#')
{
ans=I2;
ans.a[0][0]=ans.a[2][2]=1;
}
if(c[i][1]=='#')
{
ans.a[0][0]=ans.a[0][1]=ans.a[0][2]=inf;
ans.a[1][0]=ans.a[2][0]=inf;
}
if(c[i][3]=='#')
{
ans.a[2][0]=ans.a[2][1]=ans.a[2][2]=inf;
ans.a[0][2]=ans.a[1][2]=inf;
}
return ans;
}
void build(int l,int r,int p)
{
if(l==r)
{
t[p]=Make(l);
return;
}
int mid=(l+r)>>1;
build(l,mid,lp);
build(mid+1,r,rp);
t[p]=t[lp]*t[rp];
// cout<<"["<<l<<","<<r<<"] p=\n";
// for(int i=0;i<3;i++,cout<<"\n")
// for(int j=0;j<3;j++)
// cout<<t[p].a[i][j]<<" ";
// cout<<"\n\n";
}
void change(int l,int r,int x,int p)
{
if(l==r)
{
t[p]=Make(l);
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,x,lp);
else change(mid+1,r,x,rp);
t[p]=t[lp]*t[rp];
// cout<<"["<<l<<","<<r<<"] p=\n";
// for(int i=0;i<3;i++,cout<<"\n")
// for(int j=0;j<3;j++)
// cout<<t[p].a[i][j]<<" ";
// cout<<"\n\n";
}
signed main()
{
I.a[0][0]=1;
I.a[0][1]=2;
I.a[0][2]=3;
I.a[1][0]=2;
I.a[1][1]=1;
I.a[1][2]=2;
I.a[2][0]=3;
I.a[2][1]=2;
I.a[2][2]=1;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
I2.a[i][j]=inf;
cin>>n;
for(int j=1;j<=3;j++)
for(int i=1;i<=n;i++) cin>>c[i][j];
int Q=0;
cin>>Q;
build(1,n,1);
// Martix nw=t[1];
// int ans=nw.a[0][2];
// if(ans>=1e8) ans=-1;
// write(ans);
// putchar('\n');
while(Q--)
{
int x,y;
cin>>y>>x;
// cout<<x<<" "<<y<<"\n";
if(c[x][y]=='#') c[x][y]='.';
else c[x][y]='#';
change(1,n,x,1);
Martix nw=t[1];
int ans=nw.a[0][2]-1;
if(ans>=1e8) ans=-1;
write(ans);
putchar('\n');
// cout<<"\n-------------------\n\n";
}
return 0;
}
历年真题:P5024 [NOIP 2018 提高组] 保卫王国
你可以直接做,是对的。
但是我们发现答案 = 树的所有点权和 - 树的最大权独立集,这样就可以用模板题代码了。
我们对其稍加改动。
讨论区发现一种高明做法,记录一下。
首先无解好考虑。
如果必须选,则让这个点点权加 -\infty。根据 dp 的最优化决策,表示最大权独立集必须不选这个点。
如果必须不选,则让这个点点权加 \infty。根据 dp 的最优化决策,表示最大权独立集必须选这个点。
然后直接线段树区间查询以根为首重链的矩阵乘积,计算出最大权独立集,然后答案即为点权和减去这个最大权独立集。
并非。
考虑必须选一个点时我们让点权加了 -\infty,而计算最大权独立集时并不会计算这个点,所以加的 -\infty 无法被抵消,所以答案还需加上 \infty[x=0]+\infty[y=0]。
而加 \infty 后最大权独立集一定包含这个点,所以不用额外考虑。
我们查询完之后还需要还原点权。
给点权加变化量 +/-\infty 而不直接赋为 +/-\infty 的好处是不用考虑还原时的顺序问题。
Code:
#include<bits/stdc++.h>
#define int long long
#define lp (p<<1)
#define rp ((p<<1)|1)
using namespace std;
inline int read()
{
int x=0,c=getchar(),f=0;
for(;c>'9'||c<'0';f=c=='-',c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
return f?-x:x;
}
inline void write(int x)
{
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar(x%10+'0');
}
#ifndef ONLINE_JUDGE
#define ONLINE_JUDGE
#endif
const int N=1e6+5;
int n,m;
int a[N];
int fa[N];
int tail[N];
int siz[N];
int son[N];
int dfn[N];
int tod[N];
int top[N];
int id[N];
int tot;
vector<int> E[N];
const int inf=1e12;
struct Martix
{
int a[2][2]={};
}I,t[N<<2];
Martix nw;
int f[N][2],g[N][2];
int tid[N<<2];
inline int max(int x,int y) { return x>y?x:y; }
Martix operator*(const Martix &x,const Martix &y)
{
Martix ans;
ans.a[0][0]=max(x.a[0][0]+y.a[0][0],x.a[0][1]+y.a[1][0]);
ans.a[0][1]=max(x.a[0][0]+y.a[0][1],x.a[0][1]+y.a[1][1]);
ans.a[1][0]=max(x.a[1][0]+y.a[0][0],x.a[1][1]+y.a[1][0]);
ans.a[1][1]=max(x.a[1][0]+y.a[0][1],x.a[1][1]+y.a[1][1]);
return ans;
}
Martix ksm(Martix x,int p)
{
Martix ans=I;
while(p)
{
if(p&1) ans=ans*x;
x=x*x;
p>>=1;
}
return ans;
}
void dfs1(int p,int f)
{
fa[p]=f;
siz[p]=1;
for(int to:E[p])
{
if(to==f) continue;
dfs1(to,p);
siz[p]+=siz[to];
if(siz[to]>siz[son[p]]) son[p]=to;
}
}
void dfs2(int p,int tp)
{
dfn[p]=++tot;
id[tot]=p;
top[p]=tp;
tail[tp]=p;
if(son[p]) dfs2(son[p],tp);
for(int to:E[p])
if(!dfn[to]) dfs2(to,to);
}
void dfs3(int p,int fa)
{
g[p][1]=a[p];
for(int to:E[p])
{
if(to==fa) continue;
dfs3(to,p);
if(to==son[p]) continue;
g[p][1]+=f[to][0];
g[p][0]+=max(f[to][0],f[to][1]);
}
f[p][0]=g[p][0]+max(f[son[p]][0],f[son[p]][1]);
f[p][1]=g[p][1]+f[son[p]][0];
}
void pushup(int p)
{
t[p]=t[lp]*t[rp];
}
void build(int l,int r,int p)
{
if(l==r)
{
tid[id[l]]=p;
t[p].a[0][0]=t[p].a[0][1]=g[id[l]][0];
t[p].a[1][0]=g[id[l]][1];
t[p].a[1][1]=-inf;
return;
}
int mid=(l+r)>>1;
build(l,mid,lp);
build(mid+1,r,rp);
pushup(p);
}
Martix query(int l,int r,int sl,int sr,int p)
{
if(p==0) exit(0);
if(sl<=l&&r<=sr) return t[p];
int mid=(l+r)>>1;
Martix ql=I,qr=I;
if(sl<=mid) ql=query(l,mid,sl,sr,lp);
if(sr>mid) qr=query(mid+1,r,sl,sr,rp);
return ql*qr;
}
void change(int l,int r,int x,int p,const Martix &nw)
{
if(l==r)
{
t[p]=nw;
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,x,lp,nw);
else change(mid+1,r,x,rp,nw);
pushup(p);
}
int sum=0;
void change(int x,int y)
{
sum+=y;
nw=t[tid[x]];
nw.a[1][0]+=y;
a[x]+=y;
while(x)
{
Martix last=query(1,n,dfn[top[x]],dfn[tail[top[x]]],1);
change(1,n,dfn[x],1,nw);
Martix cur=query(1,n,dfn[top[x]],dfn[tail[top[x]]],1);
x=fa[top[x]];
nw=t[tid[x]];
nw.a[0][0]+=max(cur.a[0][0],cur.a[1][0])-max(last.a[0][0],last.a[1][0]);
nw.a[0][1]+=max(cur.a[0][0],cur.a[1][0])-max(last.a[0][0],last.a[1][0]);
nw.a[1][0]+=cur.a[0][0]-last.a[0][0];
}
}
signed main()
{
I.a[0][0]=I.a[1][1]=0;
I.a[0][1]=I.a[1][0]=-inf;
t[0]=I;
n=read();
m=read();
read();
for(int i=1;i<=n;i++) a[i]=read(),sum+=a[i];
for(int i=1;i<n;i++)
{
int u=read(),v=read();
E[u].push_back(v);
E[v].push_back(u);
}
dfs1(1,0);
memset(siz,0,sizeof(siz));
dfs2(1,1);
dfs3(1,0);
build(1,n,1);
while(m--)
{
int a=read(),x=read(),b=read(),y=read();
if((fa[a]==b||fa[b]==a)&&x==0&&y==0)
{
cout<<"-1\n";
continue;
}
change(a,x==0?(inf):(-inf));
change(b,y==0?(inf):(-inf));
Martix ans=query(1,n,1,dfn[tail[1]],1);
write(sum-max({ans.a[0][0],ans.a[1][0],ans.a[1][1],ans.a[0][1]})+inf*((int)(x==1)+(y==1)));
putchar('\n');
change(a,x==1?(inf):(-inf));
change(b,y==1?(inf):(-inf));
}
return 0;
}
P8820 [CSP-S 2022] 数据传输
总学习笔记同步发表于cnblogs。
总学习笔记同步发表于luogu。
主要讲下实现细节。请您完整阅读此篇文章的实现部分
倍增做法,目前效率 10 s,比题解区其他倍增做法快约 20 s,可以排在最优解第三页。
首先需要对 k 分类讨论。
考虑 $k=3$,有可能会跳出简单路径外。而跳到的简单路径外这个点一定离这个简单路径的距离 $\le 1$。于是再对每个点 $p$ 记录离他**距离**为 $1$ 的点(包括父亲)的点权最小值,设为 $minn_p$。
设 $dp_{i,j}$ 为跳到离第 $i$ 个点距离为 $j$ 时,点权和的最小值。
每次询问直接 dp。
发现可以 ddp 加速转移,拍上 ddp 即可。
这就是简单思路。
**大头戏在于实现部分。**
首先,定义广义矩阵乘法 $C_{i,j}=\min_k(A_{i,k}+B_{k,j})$。
为统一,设每个点 $p$ 的转移矩阵 $A_p$ 大小为 $3\times 3$,点权为 $v_p$,父亲为 $fa_p$,深度为 $dep_p$。
由于树上倍增是从下往上合并,考虑设答案矩阵 $B_p$:
$$B_p=\begin{bmatrix}
f_{p,0} & f_{p,1} & f_{p,2} \\
\end{bmatrix} $$
则对于一对父子节点,有如下形式转移:
$$B_p \times A_{fa_p} =B_{fa_p}$$
推广到链上:
$$
B_p \times A_{fa_p} \times A_{fa_{fa_p}} \times \dots = Ans
$$
初始矩阵
$$
B_p =
\begin{bmatrix}
v_p & \infty & \infty \\
\end{bmatrix}
$$
然后使用待定系数法建出转移矩阵即可。
考虑 $k=1$。
$$
A_p=
\begin{bmatrix}
v_p & \infty & \infty \\
\infty & \infty & \infty \\
\infty & \infty & \infty \\
\end{bmatrix}
$$
考虑 $k=2$。
$$
A_p=
\begin{bmatrix}
v_p & 0 & \infty \\
v_p & \infty & \infty \\
\infty & \infty & \infty \\
\end{bmatrix}
$$
考虑 $k=3$。
$$
A_p=
\begin{bmatrix}
v_p & 0 & \infty \\
v_p & minn_p & 0 \\
v_p & \infty & \infty \\
\end{bmatrix}
$$
然后你就直接爆炸了。因为倍增极难处理。
考虑修正,将转移矩阵下放,变为每个点存它父亲的转移矩阵。你惊奇地发现所有问题迎刃而解了!倍增初始化时仿照求 $fa$ 可以直接求出矩阵积。注意是下 $\times$ 上。
代码实现:
```cpp
for(int k=1;k<=logn[n];k++)
for(int i=1;i<=n;i++)
{
fa[k][i]=fa[k-1][fa[k-1][i]];
martix[k][i]=martix[k-1][i]*martix[k-1][fa[k-1][i]];
}
```
设两点中 $u$ 深度较小,$v$ 深度较大。
那么对于一条链的情况,就从 $v$ 倍增求 $v$ 的 $dep_v-dep_u$ 级祖先,同时维护矩阵连乘积。发现最后的那个点一定是 $u$ 的儿子节点,而矩阵连乘积正好是 $fa_v$ 到 $u$ 的转移矩阵连乘积,再用初始矩阵乘上这个积,即可得到 $B_u$,即为答案。
对于路径是两条链的情况,可以对每个点 $p$ 从 $p$ 倍增到 $dep_{lca}-dep_p-1$ 级祖先,即令**转移矩阵**连乘到 $lca$ 的儿子不乘 $B_{lca}$,最后将两条链的答案拼接起来。有大分讨。其实比较简单。
那么直接维护就可以了。
### Code:
```cpp
#include<bits/stdc++.h>
#define int long long
#if RAND_MAX==INT_MAX
#define getchar getchar_unlocked
#define putchar putchar_unlocked
#endif
using namespace std;
inline int read()
{
int x=0,c=getchar();
for(;c>'9'||c<'0';c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
return x;
}
inline void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=2e5+5;
int n,Q,k;
int a[N];
int logn[N<<1];
struct Martix
{
int a[3][3]={};
}ma[18][N],I;
int fa[18][N];
const int inf=0x3f3f3f3f3f3f3f3f;
inline int min(int x,int y) { return x<y?x:y; }
Martix operator*(const Martix &a,const Martix &b)
{
Martix ans;
memset(ans.a,0x3f,sizeof(ans.a));
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
for(int x=0;x<3;x++)
ans.a[i][j]=min(ans.a[i][j],a.a[i][x]+b.a[x][j]);
return ans;
}
vector<int> E[N];
int minn[N],tot=0;
int L[N],R[N],st[19][N<<1],dep[N];
inline int Min(int x,int y) { return dep[x]<dep[y]?x:y; }
inline int Lca(int u,int v)
{
int ql=min(L[u],L[v]);
int qr=max(R[u],R[v]);
int k=logn[qr-ql+1];
return Min(st[k][ql],st[k][qr-(1<<k)+1]);
}
inline void dfs1(int p,int f)
{
if(f) minn[p]=min(minn[p],a[f]);
++tot;
fa[0][p]=f;
L[p]=tot;
st[0][tot]=p;
dep[p]=dep[f]+1;
for(int to:E[p])
{
if(to==f) continue;
dfs1(to,p);
st[0][++tot]=p;
minn[p]=min(minn[p],a[to]);
}
R[p]=tot;
st[0][tot]=p;
}
inline void init()
{
if(k==1)
{
for(int i=1;i<=n;i++)
{
ma[0][i].a[0][0]=a[fa[0][i]];
ma[0][i].a[1][0]=inf;
ma[0][i].a[2][0]=inf;
ma[0][i].a[0][1]=inf;
ma[0][i].a[1][1]=inf;
ma[0][i].a[2][1]=inf;
ma[0][i].a[0][2]=inf;
ma[0][i].a[1][2]=inf;
ma[0][i].a[2][2]=inf;
}
}
else if(k==2)
{
for(int i=1;i<=n;i++)
{
ma[0][i].a[0][0]=a[fa[0][i]];
ma[0][i].a[1][0]=a[fa[0][i]];
ma[0][i].a[2][0]=inf;
ma[0][i].a[0][1]=0;
ma[0][i].a[1][1]=inf;
ma[0][i].a[2][1]=inf;
ma[0][i].a[0][2]=inf;
ma[0][i].a[1][2]=inf;
ma[0][i].a[2][2]=inf;
}
}
else
{
for(int i=1;i<=n;i++)
{
ma[0][i].a[0][0]=a[fa[0][i]];
ma[0][i].a[1][0]=a[fa[0][i]];
ma[0][i].a[2][0]=a[fa[0][i]];
ma[0][i].a[0][1]=0;
ma[0][i].a[1][1]=minn[fa[0][i]];
ma[0][i].a[2][1]=inf;
ma[0][i].a[0][2]=inf;
ma[0][i].a[1][2]=0;
ma[0][i].a[2][2]=inf;
}
}
for(int i=2;i<(N<<1);i++) logn[i]=logn[i>>1]+1;
for(int k=1;k<=logn[tot];k++)
for(int i=1;i<=tot-(1<<k)+1;i++)
st[k][i]=Min(st[k-1][i],st[k-1][i+(1<<(k-1))]);
for(int k=1;k<=logn[n];k++)
for(int i=1;i<=n;i++)
{
if(dep[i]<=(1<<k)) continue;
fa[k][i]=fa[k-1][fa[k-1][i]];
ma[k][i]=ma[k-1][i]*ma[k-1][fa[k-1][i]];
}
}
int f1,f2;
inline Martix query(int p,int k,int &f)
{
Martix ans=I;
if(k<=0) return I;
for(int i=logn[k];i>=0;i--)
{
if((k>>i)&1)
{
ans=ans*ma[i][p];
p=fa[i][p];
}
}
f=p;
return ans;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("P8820.in","r",stdin);
freopen("P8820.out","w",stdout);
#endif
memset(I.a,0x3f,sizeof(I.a));
memset(minn,0x3f,sizeof(minn));
I.a[0][0]=I.a[1][1]=I.a[2][2]=0;
n=read();
Q=read();
k=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
E[u].push_back(v);
E[v].push_back(u);
}
dfs1(1,0);
init();
while(Q--)
{
int u=read(),v=read(),lca=Lca(u,v);
if(dep[u]>dep[v]) swap(u,v);
if(u==lca)
{
Martix ans=query(v,dep[v]-dep[u],f1);
write(ans.a[0][0]+a[v]);
}
else
{
Martix ans1=query(u,dep[u]-dep[lca]-1,f1);
Martix ans2=query(v,dep[v]-dep[lca]-1,f2);
if(k==1)
{
write(ans1.a[0][0]+ans2.a[0][0]+a[u]+a[v]+a[lca]);
}
else if(k==2)
{
write(
min(ans1.a[0][0]+ans2.a[0][0],
min(ans1.a[0][0],ans1.a[0][1])+a[lca]+min(ans2.a[0][0],ans2.a[0][1]))
+a[u]+a[v]);
}
else
{
int ans=min({ans1.a[0][0],ans1.a[0][1],ans1.a[0][2]})+minn[f1]+ans2.a[0][0];
ans=min(ans,min({ans2.a[0][0],ans2.a[0][1],ans2.a[0][2]})+minn[f2]+ans1.a[0][0]);
if(lca!=1) ans=min(ans,min(ans1.a[0][0],ans1.a[0][1])+a[lca]+min(ans2.a[0][0],ans2.a[0][1]));
ans=min(ans,min({ans1.a[0][0],ans1.a[0][1],ans1.a[0][2]})+a[lca]+min({ans2.a[0][0],ans2.a[0][1],ans2.a[0][2]}));
ans=min(ans,min(ans1.a[0][0],ans1.a[0][1])+minn[lca]+min(ans2.a[0][0],ans2.a[0][1]));
ans=min(ans,ans1.a[0][0]+min(ans2.a[0][0],ans2.a[0][1]));
ans=min(ans,ans1.a[0][1]+ans2.a[0][0]);
write(ans+a[u]+a[v]);
}
}
putchar('\n');
}
return 0;
}
```