前言
最近感觉我的技能树有很多没有点。。
感觉这个恐怖的年代一个初中选手都要比我学得多23333
那就尽量填一下吧
总所周知,这个科技在NOIP考了
虽然当场很多部分分。。但是我一直在肝第二题。。都没有管这个
不说了不说了,眼泪都在肚子里
虽然NOIP不带修改,所以可以用倍增做
但是动态DP还是要学一学的
感觉优点有两个:
1.可以资瓷修改
2.可以询问任意一个子树的DP值
算法流程
既然是动态DP,你就肯定要把DP写出来
这里就以NOIP保卫王国为例
转移显然是长这个样子的
f
i
,
1
=
∑
m
i
n
(
f
s
o
n
,
0
,
f
s
o
n
,
1
)
+
v
i
f_{i,1}=\sum{min(f_{son,0},f_{son,1})}+v_i
fi,1=∑min(fson,0,fson,1)+vi
f
i
,
0
=
∑
f
s
o
n
,
1
f_{i,0}=\sum{f_{son,1}}
fi,0=∑fson,1
那么就可以得到一个
O
(
n
m
)
O(nm)
O(nm)的方法
考虑只有一个儿子的时候怎么做
f
i
,
1
=
m
i
n
(
f
s
o
n
,
0
,
f
s
o
n
,
1
)
+
v
i
f_{i,1}=min(f_{son,0},f_{son,1})+v_i
fi,1=min(fson,0,fson,1)+vi
f
i
,
0
=
f
s
o
n
,
1
f_{i,0}={f_{son,1}}
fi,0=fson,1
可以引入矩阵
(
f
s
o
n
,
0
0
f
s
o
n
,
1
0
)
\begin{pmatrix} f_{son,0} & 0 \\ f_{son,1} & 0 \end{pmatrix}
(fson,0fson,100)
×
\times
×
(
M
A
X
0
v
i
v
i
)
\begin{pmatrix} MAX & 0 \\ v_i & v_i \end{pmatrix}
(MAXvi0vi)
=
=
=
(
f
i
,
0
0
f
i
,
1
0
)
\begin{pmatrix} f_{i,0} & 0 \\ f_{i,1} & 0 \end{pmatrix}
(fi,0fi,100)
至于这里的矩阵乘法,就是把
∑
\sum
∑改为
m
i
n
min
min,把
×
\times
×改为
+
+
+
这样的矩阵乘法,是不是很熟悉?
我们的
f
l
o
y
d
floyd
floyd不就是这样的吗?
考虑到,
f
l
o
y
d
floyd
floyd可以用线段树来维护,可以用快速幂来加速,这个也有一样的性质
那么一条链的情况,就解决了
考虑有多个儿子怎么办?
考虑转化为一个儿子
引入树链剖分
并引入一个新的变量
g
i
,
0
g_{i,0}
gi,0,
g
i
,
1
g_{i,1}
gi,1
表示
i
i
i的非重儿子的信息,别的定义一样
下文的son都定义为重儿子
那么显然,转移可以变为
(
f
s
o
n
,
0
0
f
s
o
n
,
1
0
)
\begin{pmatrix} f_{son,0} & 0 \\ f_{son,1} & 0 \end{pmatrix}
(fson,0fson,100)
×
\times
×
(
M
A
X
g
0
g
1
+
v
i
g
1
+
v
i
)
\begin{pmatrix} MAX & g_0 \\ g_1+v_i & g_1+v_i \end{pmatrix}
(MAXg1+vig0g1+vi)
=
=
=
(
f
i
,
0
0
f
i
,
1
0
)
\begin{pmatrix} f_{i,0} & 0 \\ f_{i,1} & 0 \end{pmatrix}
(fi,0fi,100)
如果不带修改的话,一棵树也可以完成了
考虑快速维护这个东西,显然,我们只需要维护好
g
g
g就可以了
同时,我们还要维护好叶子的
f
f
f值,其实这个很好做,因为会影响叶子的只有他自己,修改的时候暴力修改一下就好了
那么任意一个点,就找到他所在重链的叶子,并把他们路径上的
g
g
g乘起来就可以得到他的
f
f
f了
至于维护g,可以发现,修改一个点的时候,只会对log个点的
g
g
g造成影响
暴力修改即可
至于怎么修改,当然是先找到对应的儿子节点,把它的贡献去掉再加回去啊
然后就大功告成了!
如果最后一个维护部分没有看懂,那么就看看代码吧23333
哦,对了,强制选和强制不选可以把他的权值改为
−
M
A
X
-MAX
−MAX或者
+
M
A
X
+MAX
+MAX
大概就这么多了
保卫王国本来就是一个模板题
如果还要,可以做P4719 【模板】动态dp
本质上是一样的,可以自己玩一玩
做完这两个,应该就可以对动态DP有一个初步的认识了吧
听说可以把树剖改为LCT,就可以省掉一个log,然而我不是很会,那么就不管了吧
时间复杂度显然是
O
(
n
l
o
g
2
n
∣
S
∣
3
)
O(nlog^2n|S|^3)
O(nlog2n∣S∣3),其中
∣
S
∣
|S|
∣S∣为矩阵的大小
常数还是蛮大的,码量也不小2333
CODE:
顺便存一个板子
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const LL MAX=1e15;
const int N=100005;
struct qu
{
int x,y,last;
}e[N*2];int num,last[N];
int n,m;
char ss[5];
LL p[N];
void init (int x,int y)
{
e[++num].x=x;e[num].y=y;e[num].last=last[x];
last[x]=num;
}
int fa[N];
int tot[N],son[N];
void dfs (int x)
{
tot[x]=1;
for (int u=last[x];u!=-1;u=e[u].last)
{
int y=e[u].y;
if (y==fa[x]) continue;
fa[y]=x;
dfs(y);
tot[x]=tot[x]+tot[y];
if (tot[y]>tot[son[x]]) son[x]=y;
}
}
int id[N],L[N],R[N],top[N],down[N];
LL f[N][2],g[N][2];
void dfs1 (int x,int tp)
{
top[x]=tp;down[tp]=x;
num++;id[num]=x;L[x]=num;
f[x][0]=g[x][0]=g[x][1]=0;f[x][1]=p[x];
if (son[x]==0) return ;
dfs1(son[x],tp);
for (int u=last[x];u!=-1;u=e[u].last)
{
int y=e[u].y;
if (y==fa[x]||y==son[x]) continue;
dfs1(y,y);
g[x][0]=g[x][0]+f[y][1];
g[x][1]=g[x][1]+min(f[y][0],f[y][1]);
}
f[x][0]=f[x][0]+g[x][0]+f[son[x]][1];
f[x][1]=f[x][1]+g[x][1]+min(f[son[x]][1],f[son[x]][0]);
}
struct qq//矩阵
{
LL f[2][2];
void clear ()
{
f[0][0]=f[0][1]=f[1][0]=f[1][1]=MAX;
}
void print ()
{
for (int u=0;u<=1;u++)
{
for (int i=0;i<=1;i++)
printf("%lld ",f[u][i]);
printf("\n");
}
}
};
qq operator * (qq x,qq y)
{
qq z;
z.clear();
for (int u=0;u<2;u++)
for (int i=0;i<2;i++)
for (int j=0;j<2;j++)
z.f[i][j]=min(x.f[i][u]+y.f[u][j],z.f[i][j]);
return z;
}
struct qt
{
int l,r;
int s1,s2;
LL g[2],f[2],v;
qq c;
void Init ()
{
c.f[0][0]=MAX;c.f[0][1]=g[0];
c.f[1][0]=g[1]+v;c.f[1][1]=g[1]+v;
}
}tr[N*2];
struct qy
{
LL f1,f2;
qy () {};
qy (LL _f1,LL _f2) {f1=_f1;f2=_f2;}
};
void Set (int now)
{
int x=id[tr[now].l];
tr[now].g[0]=g[x][0];tr[now].g[1]=g[x][1];
tr[now].f[0]=f[x][0];tr[now].f[1]=f[x][1];
tr[now].v=p[x];
tr[now].Init();
}
void update (int now)
{
int s1=tr[now].s1,s2=tr[now].s2;
tr[now].c=tr[s1].c*tr[s2].c;
}
void bt (int l,int r)
{
int now=++num;
tr[now].l=l;tr[now].r=r;
if (l==r) {Set(now);return ;}
int mid=(l+r)>>1;
tr[now].s1=num+1;bt(l,mid);
tr[now].s2=num+1;bt(mid+1,r);
update(now);
}
qq get1 (int now,int l,int r)
{
if (tr[now].l==l&&tr[now].r==r) return tr[now].c;
int mid=(tr[now].l+tr[now].r)>>1;
int s1=tr[now].s1,s2=tr[now].s2;
if (r<=mid) return get1(s1,l,r);
else if (l>mid) return get1(s2,l,r);
else return get1(s1,l,mid)*get1(s2,mid+1,r);
}
qy query (int now,int x)
{
if (tr[now].l==tr[now].r) return qy(tr[now].f[0],tr[now].f[1]);
int mid=(tr[now].l+tr[now].r)>>1;
int s1=tr[now].s1,s2=tr[now].s2;
if (x<=mid) return query(s1,x);
else return query(s2,x);
}
qy get (int x)//得到这个点的f值
{
int now=down[top[x]];
qy lalal=query(1,L[now]);
if (x==now) return lalal;
qq c=get1(1,L[x],L[now]-1);
return qy(min(lalal.f1+c.f[0][0],lalal.f2+c.f[0][1]),min(lalal.f1+c.f[1][0],lalal.f2+c.f[1][1]));
}
void rebt (int now,int x)
{
if (tr[now].l==tr[now].r) {Set(now);return ;}
int mid=(tr[now].l+tr[now].r)>>1;
int s1=tr[now].s1,s2=tr[now].s2;
if (x<=mid) rebt(s1,x);
else rebt(s2,x);
update(now);
}
void modify (int x,LL y)//我们把x这个点的权值变为y
{
f[x][1]+=y-p[x];p[x]=y;
while (x)
{
int ff=fa[top[x]],tp=top[x];
if (ff!=0)
{
qy tmp=get(tp);
g[ff][0]-=tmp.f2;
g[ff][1]-=min(tmp.f1,tmp.f2);
}
rebt(1,L[x]);
if (ff!=0)
{
qy tmp=get(tp);
g[ff][0]+=tmp.f2;
g[ff][1]+=min(tmp.f1,tmp.f2);
}
x=ff;
}
}
int main()
{
num=0;memset(last,-1,sizeof(last));
scanf("%d%d",&n,&m);scanf("%s",ss);
for (int u=1;u<=n;u++) scanf("%lld",&p[u]);
for (int u=1;u<n;u++)
{
int x,y;
scanf("%d%d",&x,&y);
init(x,y);init(y,x);
}
dfs(1);
num=0;dfs1(1,1);
num=0;bt(1,n);
while (m--)
{
int x,A,y,B;
scanf("%d%d%d%d",&x,&A,&y,&B);
if (A==0&&B==0&&(fa[x]==y||fa[y]==x)) {printf("-1\n");continue;}
LL xx=p[x],yy=p[y];
modify(x,A?p[x]-MAX:p[x]+MAX);
modify(y,B?p[y]-MAX:p[y]+MAX);
qy now=get(1);
LL ans=min(now.f1,now.f2);
if (A) ans=ans+MAX;
if (B) ans=ans+MAX;
printf("%lld\n",ans);
modify(x,xx);
modify(y,yy);
}
return 0;
}