链分治就是树链剖分。
在需要回答在子树/或结点到根路径查询时,由于 d f s dfs dfs序的连续性,可以用到树链剖分将复杂度降到 l o g 2 n log^2n log2n。
先不考虑具体如何维护每个点的 d p dp dp值,假设转移是 O ( 1 ) O(1) O(1)的,可以用线段树维护,那么修改某个点的 d p dp dp值实际上就是修改了它到根路径上的值,查询答案即为根结点上的值。
在链分治后,只会跳 l o g n logn logn次链,每次链内/链与链交接处都可以 l o g n logn logn修改。
求树上的最大权独立集的权值大小。
设 f [ x ] [ 1 / 0 ] f[x][1/0] f[x][1/0]分别表示选/不选点 x x x时, x x x子树上的最大权独立集的权值大小,
正常的 D P DP DP转移:
f [ x ] [ 0 ] = m a x y ∈ s o n x ( f [ y ] [ 0 ] , f [ y ] [ 1 ] ) f[x][0]=max_{y\in son_x}(f[y][0],f[y][1]) f[x][0]=maxy∈sonx(f[y][0],f[y][1])
f [ x ] [ 1 ] = v a l x + m a x y ∈ s o n x f [ y ] [ 0 ] f[x][1]=val_x+max_{y\in son_x}f[y][0] f[x][1]=valx+maxy∈sonxf[y][0]
为了达到每次链内/链与链交接处都可以 l o g n logn logn修改,我们显然不能对于每个点逐个儿子的转移,而需要把信息压成重儿子和轻儿子两部分。
设 g [ x ] [ 1 / 0 ] g[x][1/0] g[x][1/0]分别表示不考虑重儿子情况下,选/不选点 x x x时, x x x子树上的最大权独立集的权值大小。
现在的 D P DP DP转移:
f [ x ] [ 0 ] = g [ x ] [ 0 ] + m a x ( f [ s o n x [ 0 ] , f [ s o n x ] [ 1 ] ] ) f[x][0]=g[x][0]+max(f[son_x[0],f[son_x][1]]) f[x][0]=g[x][0]+max(f[sonx[0],f[sonx][1]])
f [ x ] [ 1 ] = g [ x ] [ 1 ] + f [ s o n x ] [ 0 ] f[x][1]=g[x][1]+f[son_x][0] f[x][1]=g[x][1]+f[sonx][0]
似乎可以 O ( 1 ) O(1) O(1)合并了,线性的加和取 m a x max max。
考虑维护连续的一段线性的加和取 m a x max max的方法,我们联想到了矩阵。
上述转移可以仿照 F l o y d Floyd Floyd的形式写成矩阵乘法:
[ f [ x ] [ 0 ] f [ x ] [ 1 ] ] = [ g [ x ] [ 0 ] g [ x ] [ 0 ] g [ x ] [ 1 ] − i n f ] × [ f [ s o n x ] [ 0 ] f [ s o n x ] [ 1 ] ] \left[ \begin{matrix} f[x][0] \\ f[x][1] \end{matrix} \right]= \left[ \begin{matrix} g[x][0]& g[x][0]\\ g[x][1] & -inf \end{matrix} \right]\times \left[ \begin{matrix} f[son_x][0]\\ f[son_x][1] \end{matrix} \right] [f[x][0]f[x][1]]=[g[x][0]g[x][1]g[x][0]−inf]×[f[sonx][0]f[sonx][1]]
而对于 t o p = x top=x top=x的这条重链,不断向下展开连乘式,得到:
[ f [ x ] [ 0 ] f [ x ] [ 1 ] ] = [ g [ x ] [ 0 ] g [ x ] [ 0 ] g [ x ] [ 1 ] − i n f ] × [ g [ s o n x ] [ 0 ] g [ s o n x ] [ 0 ] g [ s o n x ] [ 1 ] − i n f ] × [ g [ s o n s o n x ] [ 0 ] g [ s o n s o n x ] [ 0 ] g [ s o n s o n x ] [ 1 ] − i n f ] . . . \left[ \begin{matrix} f[x][0] \\ f[x][1] \end{matrix} \right]= \left[ \begin{matrix} g[x][0]& g[x][0]\\ g[x][1] & -inf \end{matrix} \right]\times \left[ \begin{matrix} g[son_x][0]& g[son_x][0]\\ g[son_x][1] & -inf \end{matrix} \right]\times \left[ \begin{matrix} g[son_{son_x}][0]& g[son_{son_x}][0]\\ g[son_{son_x}][1] & -inf \end{matrix} \right]... [f[x][0]f[x][1]]=[g[x][0]g[x][1]g[x][0]−inf]×[g[sonx][0]g[sonx][1]g[sonx][0]−inf]×[g[sonsonx][0]g[sonsonx][1]g[sonsonx][0]−inf]...
并且链分治后 x x x和 s o n x son_x sonx的 d f s dfs dfs序是连续的,所以可以用线段树维护一段连续区间的矩阵的连乘积。
每个结点的矩阵长这样:
[ g [ x ] [ 0 ] g [ x ] [ 0 ] g [ x ] [ 1 ] − i n f ] \left[ \begin{matrix} g[x][0]& g[x][0]\\ g[x][1] & -inf \end{matrix} \right] [g[x][0]g[x][1]g[x][0]−inf]
查询直接回答根节点的值即可。
我们发现修改操作 ( x , y ) (x,y) (x,y)只需要修改 x x x的矩阵和 x x x不断跳链到达根节点过程中经过的每个链顶结点的矩阵。
所以将复杂度转成了:
在链分治后,只会跳 l o g n logn logn次链,每次链内/链与链交接处都可以 l o g n logn logn修改。
luoguP4719 【模板】动态dp
#include
#define mid ((l+r)>>1)
#define lc k<<1
#define rc k<<1|1
using namespace std;
const int N=1e5+10;
int n,m;
int val[N],dp[N][2];
int head[N],to[N<<1],nxt[N<<1],tot;
int df[N],rv[N],ed[N],dfn;
int sz[N],top[N],f[N],son[N];
struct Mat{
int g[2][2];
Mat operator *(const Mat&ky)const{
Mat re;
re.g[0][0]=max(g[0][0]+ky.g[0][0],g[0][1]+ky.g[1][0]);
re.g[0][1]=max(g[0][0]+ky.g[0][1],g[0][1]+ky.g[1][1]);
re.g[1][0]=max(g[1][0]+ky.g[0][0],g[1][1]+ky.g[1][0]);
re.g[1][1]=max(g[1][0]+ky.g[0][1],g[1][1]+ky.g[1][1]);
return re;
}
}t[N<<2],a[N],tp,C,D;
inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
void dfs(int x)
{
sz[x]=1;dp[x][1]=val[x];
for(int j,i=head[x];i;i=nxt[i]){
j=to[i];if(j==f[x]) continue;
f[j]=x;dfs(j);sz[x]+=sz[j];
if(sz[j]>sz[son[x]]) son[x]=j;
dp[x][1]+=dp[j][0];dp[x][0]+=max(dp[j][1],dp[j][0]);
}
}
void dfss(int x,int tpo)
{
top[x]=tpo;df[x]=++dfn;rv[dfn]=x;
if(!son[x]) {ed[tpo]=dfn;return;}
dfss(son[x],tpo);
for(int j,i=head[x];i;i=nxt[i]){
j=to[i];if(j==f[x] || j==son[x]) continue;
dfss(j,j);
}
}
void build(int k,int l,int r)
{
if(l==r){
int x=rv[l];
a[l].g[0][0]=a[l].g[0][1]=dp[x][0]-max(dp[son[x]][0],dp[son[x]][1]);
a[l].g[1][0]=dp[x][1]-dp[son[x]][0];
t[k]=a[l];return;
}
build(lc,l,mid);build(rc,mid+1,r);
t[k]=t[lc]*t[rc];
}
Mat ask(int k,int l,int r,int L,int R)
{
if(L<=l && r<=R) return t[k];
if(R<=mid) return ask(lc,l,mid,L,R);
if(L>mid) return ask(rc,mid+1,r,L,R);
return ask(lc,l,mid,L,R)*ask(rc,mid+1,r,L,R);
}
void modify(int k,int l,int r,int pos)
{
if(l==r){t[k]=a[l];return;}
if(pos<=mid) modify(lc,l,mid,pos);
else modify(rc,mid+1,r,pos);
t[k]=t[lc]*t[rc];
}
inline void chg(int x,int y)
{
int z=df[x];
a[z].g[1][0]+=(y-val[x]);val[x]=y;
for(;;){
C=ask(1,1,n,df[top[x]],ed[top[x]]);
modify(1,1,n,z);
D=ask(1,1,n,df[top[x]],ed[top[x]]);
x=f[top[x]];if(!x) break;
z=df[x];
a[z].g[0][0]+=(max(D.g[0][0],D.g[1][0])-max(C.g[0][0],C.g[1][0]));
a[z].g[0][1]=a[z].g[0][0];
a[z].g[1][0]+=(D.g[0][0]-C.g[0][0]);
}
}
int main(){
int i,j,x,y;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i) scanf("%d",&val[i]);
for(i=1;i<n;++i){
scanf("%d%d",&x,&y);
lk(x,y);lk(y,x);
}
dfs(1);dfss(1,1);
build(1,1,n);
for(;m;--m){
scanf("%d%d",&x,&y);
chg(x,y);tp=ask(1,1,n,1,ed[1]);
printf("%d\n",max(tp.g[0][0],tp.g[1][0]));
}
return 0;
}
树剖虽然常数小,但复杂度终究是 O ( l o g 2 n ) O(log^2n) O(log2n)的。
LCT虽然复杂度是均摊 O ( l o g n ) O(logn) O(logn),但常数终究是大的。
每次都询问某个叶子结点到根节点的连乘积,这是一个关键的特殊性质。
由于树是静态的,所以考虑截取 L C T LCT LCT的部分操作,使用全局平衡二叉树将复杂度降到严格 O ( l o g n ) O(logn) O(logn)。
考虑对于每条重链建一个 b s t bst bst,重链之间连上虚边。而建森林的时候将每个点加权使得其尽量平衡。
具体方法为:
对于一条重链,每个点的权值设为它的轻儿子的 s i z e size size之和+1(加不加一不重要,只是这样好算: v a l = s z [ x ] − s z [ s o n x ] val=sz[x]-sz[son_x] val=sz[x]−sz[sonx]),找到带权重心后,把它作为这一层结点,递归左右区间建立左右儿子。
重链之间的虚边按照原树形态建就好了。
考虑这样建出来的树,每次向上跳一次(虚边/实边),轻儿子的总 s i z e size size至少翻一倍,所以树高是 l o g n logn logn的。
每个点维护自己的矩阵和 b s t bst bst内的子树连乘积,修改的同时更新上来即可(如果是虚边还需要改一下父亲的矩阵)。
luoguP4751 动态dp【加强版】
#include
#define gc getchar
using namespace std;
const int N=1e6+10;
int n,m,rt,ans;
int val[N],ch[N][2];
int head[N],to[N<<1],nxt[N<<1],tot;
int stk[N],tl,siz[N],sz[N],f[N],son[N];
bool vs[N];
char cp,OS[100];
inline void rd(int &x)
{
cp=gc();x=0;int f=0;
for(;!isdigit(cp);cp=gc()) if(cp=='-') f=1;
for(;isdigit(cp);cp=gc()) x=(x<<3)+(x<<1)+(cp^48);
if(f) x=-x;
}
inline void ot(int x)
{
int re=0;
for(;(!re)||(x);x/=10) OS[++re]='0'+x%10;
for(;re;--re) putchar(OS[re]);
putchar('\n');
}
struct Mat{
int g[2][2];
Mat(){g[0][0]=g[0][1]=g[1][0]=g[1][1]=-0x3f3f3f3f;}
Mat operator *(const Mat&ky)const{
Mat re;int i,j,k;
for(i=0;i<2;++i)
for(j=0;j<2;++j)
for(k=0;k<2;++k)
re.g[i][j]=max(re.g[i][j],g[i][k]+ky.g[k][j]);
return re;
}
inline void zs(int x){g[1][0]=x;g[0][0]=g[0][1]=0;}
inline void itia(){g[1][1]=g[0][0]=0;}
}a[N],b[N];
inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
inline void pu(const int &x){b[x]=b[ch[x][0]]*a[x]*b[ch[x][1]];}
void dfs(int x,int fr)
{
sz[x]=1;
for(int j,i=head[x];i;i=nxt[i]){
j=to[i];if(j==fr) continue;
dfs(j,x);sz[x]+=sz[j];
if(sz[j]>sz[son[x]]) son[x]=j;
}
}
int sbuild(int l,int r)
{
if(l>r) return 0;
int pos=lower_bound(siz+l,siz+r+1,(siz[r]-siz[l-1]-1)/2+1+siz[l-1])-siz,x,lc,rc;
x=stk[pos];
lc=sbuild(l,pos-1);
rc=sbuild(pos+1,r);
f[lc]=f[rc]=x;ch[x][0]=lc;ch[x][1]=rc;pu(x);
return x;
}
int build(int x)
{
int k,u,i,j;
for(k=x;k;k=son[k]) vs[k]=true;
for(k=x;k;k=son[k])
for(i=head[k];i;i=nxt[i]){
j=to[i];if(vs[j]) continue;
f[(u=build(j))]=k;
a[k].g[0][0]+=max(b[u].g[0][0],b[u].g[1][0]);
a[k].g[0][1]=a[k].g[0][0];
a[k].g[1][0]+=b[u].g[0][0];
}
for(tl=0,i=x;i;i=son[i]) {
stk[++tl]=i;
siz[tl]=siz[tl-1]+sz[i]-sz[son[i]];
}
return sbuild(1,tl);
}
inline bool isr(int x){
return ((ch[f[x]][0]!=x)&&(ch[f[x]][1]!=x));}
inline void chg(int x,int y)
{
a[x].g[1][0]+=(y-val[x]);val[x]=y;
for(;x;x=f[x]){
if(f[x]&&isr(x)){
y=f[x];
a[y].g[0][0]-=max(b[x].g[0][0],b[x].g[1][0]);
a[y].g[1][0]-=b[x].g[0][0];pu(x);
a[y].g[0][0]+=max(b[x].g[0][0],b[x].g[1][0]);
a[y].g[1][0]+=b[x].g[0][0];
a[y].g[0][1]=a[y].g[0][0];
}else pu(x);
}
}
int main(){
int i,x,y;
a[0].itia();b[0].itia();
rd(n);rd(m);
for(i=1;i<=n;++i) {rd(val[i]);a[i].zs(val[i]);}
for(i=1;i<n;++i){rd(x);rd(y);lk(x,y);lk(y,x);}
dfs(1,0);rt=build(1);
for(;m;--m){
rd(x);rd(y);x^=ans;
chg(x,y);
ot(ans=max(b[rt].g[0][0],b[rt].g[1][0]));
}
return 0;
}