先摆一道模板题。
(里面的最大权独立集也就是指选若干个点,这些点两两之间没有直接连边,然后使点权和最大)
假如这是一道静态的题,大家肯定随手就写出方程了:设
f
i
,
0
f_{i,0}
fi,0 表示不选
i
i
i 的最优解,
f
i
,
1
f_{i,1}
fi,1 表示选
i
i
i 的最优解,那么有(其中
s
o
n
son
son 表示
i
i
i 的儿子):
{
f
i
,
0
=
∑
max
(
f
s
o
n
,
0
,
f
s
o
n
,
1
)
f
i
,
1
=
v
a
l
i
+
∑
f
s
o
n
,
0
\begin{cases} f_{i,0}=\sum \max(f_{son,0},f_{son,1})\\ f_{i,1}=val_i+\sum f_{son,0} \end{cases}
{fi,0=∑max(fson,0,fson,1)fi,1=vali+∑fson,0
发现这个东西有点像矩阵乘法的形式,我们改变一下矩阵乘法的规则——原来的规则是这样的:
A
×
B
=
C
→
C
i
,
j
=
∑
k
=
1
n
A
i
,
k
×
B
i
,
j
A\times B=C \to C_{i,j}=\sum_{k=1}^n A_{i,k}\times B_{i,j}
A×B=C→Ci,j=k=1∑nAi,k×Bi,j
现在改成这样:
A
×
B
=
C
→
C
i
,
j
=
max
k
=
1
n
(
A
i
,
k
+
B
i
,
j
)
A\times B=C \to C_{i,j}=\max_{k=1}^n (A_{i,k}+B_{i,j})
A×B=C→Ci,j=k=1maxn(Ai,k+Bi,j)
为了方便,先考虑只有一个儿子的情况。那么转移方程可以表示成:
(
0
0
v
a
l
i
−
i
n
f
)
×
(
f
s
o
n
,
0
f
s
o
n
,
1
)
=
(
f
i
,
0
f
i
,
1
)
\left( \begin{array}{c} 0 & 0\\ val_{i} & -inf \end{array} \right)\times \left( \begin{array}{c} f_{son,0}\\ f_{son,1} \end{array} \right)= \left( \begin{array}{c} f_{i,0}\\ f_{i,1} \end{array} \right)
(0vali0−inf)×(fson,0fson,1)=(fi,0fi,1)
但事实上是不可能只有一个儿子的,考虑转化为一个儿子来做,也就是树链剖分了。
(下面的 s o n son son指重儿子)
但是还需要维护非重儿子的信息,我们定义一个新的数组
g
g
g,
g
i
,
0
g_{i,0}
gi,0 表示不选
i
i
i 时
i
i
i 的非重儿子的子树的最优解,
g
i
,
1
g_{i,1}
gi,1 表示选
i
i
i 时
i
i
i 的非重儿子的最优解,那么
g
g
g 的转移方程为(
j
j
j 是在枚举儿子):
{
g
i
,
0
=
∑
max
(
f
j
,
0
,
f
j
,
1
)
(
j
≠
s
o
n
)
g
i
,
1
=
∑
f
j
,
0
(
j
≠
s
o
n
)
\begin{cases} g_{i,0}=\sum \max (f_{j,0},f_{j,1})~~(j\ne son)\\ g_{i,1}=\sum f_{j,0}~~~~~~~~~~~~~~~~~~~~(j\ne son) \end{cases}
{gi,0=∑max(fj,0,fj,1) (j=son)gi,1=∑fj,0 (j=son)
说白了, g g g 和 f f f 的定义差不多,但是不考虑重链而已。
那么转移的矩阵就变成(下面称这条柿子最左边的那个矩阵为
g
g
g 矩阵):
(
g
i
,
0
g
i
,
0
v
a
l
i
+
g
i
,
1
−
i
n
f
)
×
(
f
s
o
n
,
0
f
s
o
n
,
1
)
=
(
f
i
,
0
f
i
,
1
)
\left( \begin{array}{c} g_{i,0} & g_{i,0}\\ val_{i}+g_{i,1} & -inf \end{array} \right)\times \left( \begin{array}{c} f_{son,0}\\ f_{son,1} \end{array} \right)= \left( \begin{array}{c} f_{i,0}\\ f_{i,1} \end{array} \right)
(gi,0vali+gi,1gi,0−inf)×(fson,0fson,1)=(fi,0fi,1)
那么我们在每个点上把它对应的
g
g
g 矩阵存下来,叶子结点特判一下,存一个
f
f
f 矩阵,那么因为每条重链的底部一定是叶子结点,所以要求某一个点的
f
f
f 矩阵的话,只需要把他所在的重链的底部
到它
的路径上的所有点的矩阵乘起来就可以得到了。
但是这里还可以取取巧,因为叶子结点的 g i , 0 g_{i,0} gi,0 和 g i , 1 g_{i,1} gi,1 与 f i , 0 f_{i,0} fi,0 和 f i , 1 f_{i,1} fi,1 相同,所以叶子结点我们不妨也放它的 g g g 矩阵,这样的效果是不会变的,只是有两个数是废的,问题不大。这样的话就不需要特判了,所有的点都放 g g g 矩阵即可。
修改的话维护 g g g 矩阵即可,显然,只有 l o g n logn logn 个点的 g g g 矩阵会发生改变,所以复杂度可以保证。
具体的修改也就是将这个权值被修改的点沿着重链往上跳,每一条重链的父亲就是要修改的对象,让这个对象根据现在的 f f f 来调整即可。
具体的调整: g g g 矩阵中 g i , 0 g_{i,0} gi,0 的值为所有非重儿子的 max ( f j , 0 , f j , 1 ) \max(f_{j,0},f_{j,1}) max(fj,0,fj,1),那么让 g i , 0 g_{i,0} gi,0 减去原来的 max \max max 值再加上现在的 max \max max 值即可。 g i , 1 g_{i,1} gi,1 是 f j , 1 f_{j,1} fj,1 之和,那么只需要减去原来的 f j , 1 f_{j,1} fj,1 再加上现在的 f j , 1 f_{j,1} fj,1 即可。 − i n f -inf −inf 那一位不需要修改。
那么代码如下:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010
#define inf 999999999
int n,m;
struct nod{int y,next;};
nod e[maxn*2];
int first[maxn];
void buildroad(int x,int y)
{
static int len=0;
e[++len]=(nod){y,first[x]};
first[x]=len;
}
int val[maxn];
int fa[maxn],size[maxn],mson[maxn];
void dfs1(int x)//树链剖分,找重儿子
{
size[x]=1;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==fa[x])continue;
fa[y]=x;
dfs1(y);
size[x]+=size[y];
if(size[y]>size[mson[x]])mson[x]=y;
}
}
int now[maxn],old[maxn],id=0,top[maxn];
int bottom[maxn],cnt=0,be[maxn];
void dfs2(int x,int tp,int belong)//树链剖分,重新编号
{
now[x]=++id;old[id]=x;top[x]=tp;be[x]=belong;
//be[x]是x所处的重链的编号,bottom[x]是编号为x的重链的最低端的结点
if(mson[x]==0){bottom[belong]=x;return;}
dfs2(mson[x],tp,belong);
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=fa[x]&&e[i].y!=mson[x])dfs2(e[i].y,e[i].y,++cnt);
}
struct matrix{//矩阵
int a[2][2];
matrix(){memset(a,-63,sizeof(a));}
void set(int s[2][2])
{
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
a[i][j]=s[i][j];
}
matrix operator *(const matrix b)
{
matrix c;
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
for(int k=0;k<2;k++)
c.a[i][j]=max(c.a[i][j],a[i][k]+b.a[k][j]);//注意这里的矩阵的计算规则是重新定义的
return c;
}
};
int g[maxn][2][2],f[maxn][2];//g是记录每个点的g矩阵,f是用来辅助dp的
/*
g[i][0] , g[i][0]
g[i][1] , -MAX
*/
struct node{//线段树
int l,r;
matrix z;
node *zuo,*you;
node(int x,int y)
{
l=x,r=y;
if(l<r)
{
int mid=l+r>>1;
zuo=new node(l,mid);
you=new node(mid+1,r);
z=zuo->z*you->z;//注意是左儿子乘右儿子
}
else zuo=you=NULL,z.set(g[old[l]]);
}
void change(int x)//更新x的矩阵
{
if(l==r){z.set(g[old[l]]);return;}
int mid=l+r>>1;
if(x<=mid)zuo->change(x);
else you->change(x);
z=zuo->z*you->z;
}
matrix ask(int x,int y)//询问x到y的矩阵乘积
{
if(l==x&&r==y)return z;
int mid=l+r>>1;
if(y<=mid)return zuo->ask(x,y);
else if(x>=mid+1)return you->ask(x,y);
else return zuo->ask(x,mid)*you->ask(mid+1,y);
}
};
node *root=NULL;
void dp(int x)//预处理
{
g[x][1][1]=-inf;g[x][1][0]=val[x];
f[x][1]=val[x];//这里的f是原dp方程中的f,用来辅助求出g的
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==fa[x])continue;
dp(y);
f[x][0]+=max(f[y][0],f[y][1]);
f[x][1]+=f[y][0];
if(y!=mson[x])
{
g[x][0][0]+=max(f[y][0],f[y][1]);
g[x][1][0]+=f[y][0];
}
}
g[x][0][1]=g[x][0][0];
}
matrix before,after;
void change(int x,int y)
{
g[x][1][0]+=y-val[x];//修改
while(x!=0)
{
before=root->ask(now[top[x]],now[bottom[be[x]]]);//先求出原来的
root->change(now[x]);//更新矩阵
after=root->ask(now[top[x]],now[bottom[be[x]]]);//求出现在的
x=fa[top[x]];//往上跳
g[x][0][0]+=max(after.a[0][0],after.a[1][0])-max(before.a[0][0],before.a[1][0]);
g[x][1][0]+=after.a[0][0]-before.a[0][0];
g[x][0][1]=g[x][0][0];//更新这个位置的矩阵
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&val[i]);
for(int i=1,x,y;i<n;i++)
{
scanf("%d %d",&x,&y);
buildroad(x,y);
buildroad(y,x);
}
dfs1(1);
dfs2(1,1,++cnt);
dp(1);
root=new node(1,n);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
change(x,y);val[x]=y;
matrix ans=root->ask(now[1],now[bottom[be[1]]]);
printf("%d\n",max(ans.a[0][0],ans.a[1][0]));
}
}