动态 dp

这两天疯狂学东西,不管是有用算法还是无用算法。大概是真的打不动模拟赛了,也不想做题。

今天模拟赛 T1 计算几何 T2 构造 + 计算几何 T3 手玩十组样例。很好奇出题人是不是玩了若干时间原神之后整出这种阴间活来。

动态 dp

这种东西一般是把一个很显然的树形 dp 给你挂个带修。当然也可能是不显然的树形 dp。

不过板子最起码还是挺显然的树形 dp。就是最大独立集。有经典的 dp:设 \(dp_{x,0/1}\)\(x\) 子树选 / 不选 \(x\) 的答案,转移显然

\[f_{x,0}=\sum_{v\in son_x}\max(f_{v,0},f_{v,1}) \]

\[f_{x,1}=a_x+\sum_{v\in son_x}f_{v,0} \]

最后答案显然 \(\max(f_{1,0},f_{1,1})\)

考虑带修。在序列上带修查区间的 dp 值我们常用线段树维护矩阵矩阵表示 dp。因此我们考虑使用矩阵表示一下 dp,然后使用树剖做修改。

那么我们考虑把转移矩阵写出来。因为我们要树剖,所以要从重儿子的 dp 值转移到当前节点。那么对于所有轻儿子设 \(g_{x,0}\)\(x\) 所有轻儿子可选可不选的最大值,\(g_{x,1}\) 是都不选的最大值。那么有

\[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}+a_x \]

那为了方便我们把 \(a_x\) 加到 \(g_{x,1}\) 里边。这样就只有 \(f,g\) 了。

看着已经有点矩阵的样子了。那么定义广义矩阵乘法,把普通矩阵乘的加换成 \(\max\),乘换成加。它是满足结合律的。实际上只要乘满足结合律,乘对加满足分配律,那么矩阵乘就满足结合律。因此上边写成广义矩阵乘就长这样:

\[\begin{bmatrix}f_{x,0}\\f_{x,1}\end{bmatrix}=\begin{bmatrix}g_{x,0} & g_{x,0}\\g_{x,1} & -\infty\end{bmatrix}\begin{bmatrix}f_{son_x,0}\\f_{son_x,1}\end{bmatrix} \]

那么每次修改就只需要在重链头的位置改一下这条链的贡献就可以了。注意乘的时候乘的是整条重链,因此每个节点要多维护一个链底的位置。

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int inf=1e9;
int n,m,a[100010];
struct Mat{
	int a[3][3];
	Mat(){memset(a,-0x3f,sizeof(a));}
	Mat operator*(const Mat&s)const{
		Mat ret;
		for(int i=1;i<=2;i++)for(int j=1;j<=2;j++)for(int k=1;k<=2;k++)ret.a[i][j]=max(ret.a[i][j],a[i][k]+s.a[k][j]);
		return ret;
	}
}tmp[100010];
struct gra{
	int v,next;
}edge[200010];
int t,head[100010];
void add(int u,int v){
	edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int num,dfn[100010],rk[100010],dep[100010],fa[100010],size[100010],son[100010],ed[100010],top[100010];
void dfs1(int x,int f){
	dep[x]=dep[f]+1;fa[x]=f;size[x]=1;
	for(int i=head[x];i;i=edge[i].next){
		if(edge[i].v!=f){
			dfs1(edge[i].v,x);
			size[x]+=size[edge[i].v];
			if(size[son[x]]<size[edge[i].v])son[x]=edge[i].v;
		}
	}
}
void dfs2(int x,int f,int tp){
	dfn[x]=++num;rk[num]=x;top[x]=tp;
	if(son[x])dfs2(son[x],x,tp),ed[x]=ed[son[x]];
	else ed[x]=x;
	for(int i=head[x];i;i=edge[i].next){
		if(edge[i].v!=f&&edge[i].v!=son[x])dfs2(edge[i].v,x,edge[i].v);
	}
}
int dp[100010][2];
void dfs(int x,int f){
	dp[x][0]=0;dp[x][1]=a[x];
	for(int i=head[x];i;i=edge[i].next){
		if(edge[i].v!=f){
			dfs(edge[i].v,x);
			dp[x][0]+=max(dp[edge[i].v][0],dp[edge[i].v][1]);
			dp[x][1]+=dp[edge[i].v][0];
		}
	}
}
#define lson rt<<1
#define rson rt<<1|1
Mat tree[400010];
void pushup(int rt){
	tree[rt]=tree[lson]*tree[rson];
}
void build(int rt,int l,int r){
	if(l==r){
		int x=rk[l],g0=0,g1=a[x];
		for(int i=head[x];i;i=edge[i].next){
			if(edge[i].v!=fa[x]&&edge[i].v!=son[x]){
				g0+=max(dp[edge[i].v][0],dp[edge[i].v][1]);
				g1+=dp[edge[i].v][0];
			}
		}
		tree[rt].a[1][1]=g0;tree[rt].a[1][2]=g0;tree[rt].a[2][1]=g1;tree[rt].a[2][2]=-inf;
		tmp[l]=tree[rt];return;
	}
	int mid=(l+r)>>1;
	build(lson,l,mid);build(rson,mid+1,r);
	pushup(rt);
}
void update(int rt,int L,int R,int pos){
	if(L==R){
		tree[rt]=tmp[pos];return;
	}
	int mid=(L+R)>>1;
	if(pos<=mid)update(lson,L,mid,pos);
	else update(rson,mid+1,R,pos);
	pushup(rt);
}
Mat query(int rt,int L,int R,int l,int r){
	if(l<=L&&R<=r)return tree[rt];
	int mid=(L+R)>>1;
	if(l>mid)return query(rson,mid+1,R,l,r);
	if(r<=mid)return query(lson,L,mid,l,r);
	return query(lson,L,mid,l,r)*query(rson,mid+1,R,l,r);
}
void change(int x,int val){
	tmp[dfn[x]].a[2][1]+=val-a[x];a[x]=val;
	while(x){
		Mat a=query(1,1,n,dfn[top[x]],dfn[ed[x]]);
		update(1,1,n,dfn[x]);
		Mat b=query(1,1,n,dfn[top[x]],dfn[ed[x]]);
		x=fa[top[x]];
		if(!x)break;
		int g0=a.a[1][1],g1=a.a[2][1],f0=b.a[1][1],f1=b.a[2][1];
		tmp[dfn[x]].a[1][1]=tmp[dfn[x]].a[1][2]=tmp[dfn[x]].a[1][1]+max(f0,f1)-max(g0,g1);
		tmp[dfn[x]].a[2][1]+=f0-g0;
	}
}
signed main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<n;i++){
		int u,v;scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	dfs1(1,0);dfs2(1,0,1);dfs(1,0);build(1,1,n);
	while(m--){
		int x,val;scanf("%d%d",&x,&val);
		change(x,val);
		Mat ans=query(1,1,n,dfn[1],dfn[ed[1]]);
		printf("%d\n",max(ans.a[1][1],ans.a[2][1]));
	}
	return 0;
}

加强版

好了树剖跑不过去了。要求复杂度 \(O(n\log n)\)

容易想到使用 LCT 进行操作,满足复杂度要求。然而常数巨大,大概还没有树剖跑的快。

有一种神奇数据结构叫做全局平衡二叉树,差不多就是静态的 LCT。

矩阵定义同上,考虑怎么维护它们的乘积。仍然重链剖分,每条重链用一棵比较平衡的树来维护,并类似 LCT 保证深度依中序遍历顺序递增。由于它是静态的,因此我们只需要让它构造的时候深度不超过 \(O(\log n)\)

建树的时候可以记录每个点的轻子树的大小 \(+1\) 作为权值,然后对于一条重链记录加权重心作为根,左右递归建树。容易发现子树的大小为父亲的一半,因此树高是 \(O(\log n)\) 级别的。

修改的时候考虑在建树时类似 LCT 的把轻儿子向父亲连虚边,然后爆跳父亲每次 pushup 就是对的。正确性不会证,复杂度不会证。问就是均摊。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
int n,m;
struct node{
    int a[3][3];
    node(){memset(a,-0x3f,sizeof(a));}
    node operator*(const node&s)const{
        node ret;
        for(int i=1;i<=2;i++)for(int j=1;j<=2;j++)for(int k=1;k<=2;k++){
            ret.a[i][j]=max(ret.a[i][j],a[i][k]+s.a[k][j]);
        }
        return ret;
    }
};
struct gra{
    int v,next;
}edge[2000010];
int t,head[1000010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int size[1000010],son[1000010],g[1000010][2],a[1000010];
void dfs1(int x,int f){
    size[x]=1;g[x][1]=a[x];
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs1(edge[i].v,x);
            size[x]+=size[edge[i].v];
            if(size[son[x]]<size[edge[i].v])son[x]=edge[i].v;
            g[x][0]+=max(g[edge[i].v][0],g[edge[i].v][1]);
            g[x][1]+=g[edge[i].v][0];
        }
    }
}
void dfs2(int x,int f){
    g[x][0]-=max(g[son[x]][0],g[son[x]][1]);
    g[x][1]-=g[son[x]][0];
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f)dfs2(edge[i].v,x);
    }
}
struct Tree{
    #define lson tree[x].son[0]
    #define rson tree[x].son[1]
    int son[2],fa,size;
    node val,sum;
}tree[1000010];
int rt,stk[1000010];
bool isroot(int x){
    return x!=tree[tree[x].fa].son[0]&&x!=tree[tree[x].fa].son[1];
}
void pushup(int x){
    tree[x].sum=tree[rson].sum*tree[x].val*tree[lson].sum;
}
int build(int l,int r){
    if(l>r)return 0;
    int sum=0;
    for(int i=l;i<=r;i++)sum+=tree[stk[i]].size;
    int cnt=0;
    for(int i=l;i<=r;i++){
        cnt+=tree[stk[i]].size;
        if((cnt<<1)>=sum){
            int rs=build(l,i-1),ls=build(i+1,r);
            tree[stk[i]].son[0]=ls;tree[stk[i]].son[1]=rs;
            tree[ls].fa=tree[rs].fa=stk[i];
            pushup(stk[i]);
            return stk[i];
        }
    }
    return 0;
}
int dfs(int tp,int f){
    for(int x=tp;x;f=x,x=son[x]){
        for(int i=head[x];i;i=edge[i].next){
            if(edge[i].v!=son[x]&&edge[i].v!=f){
                tree[dfs(edge[i].v,x)].fa=x;
            }
        }
        tree[x].val.a[1][1]=tree[x].val.a[1][2]=g[x][0];
        tree[x].val.a[2][1]=g[x][1];
    }
    int top=0;
    for(int x=tp;x;x=son[x])stk[++top]=x,tree[x].size=size[x]-size[son[x]];
    return build(1,top);
}
void update(int x,int val){
    g[x][1]+=val-a[x];a[x]=val;
    for(;x;x=tree[x].fa){
        int f0=max(tree[x].sum.a[1][1],tree[x].sum.a[1][2]);
        int f1=max(tree[x].sum.a[2][1],tree[x].sum.a[2][2]);
        tree[x].val.a[1][1]=tree[x].val.a[1][2]=g[x][0];
        tree[x].val.a[2][1]=g[x][1];
        pushup(x);
        int g0=max(tree[x].sum.a[1][1],tree[x].sum.a[1][2]);
        int g1=max(tree[x].sum.a[2][1],tree[x].sum.a[2][2]);
        if(isroot(x)){
            g[tree[x].fa][0]+=max(g0,g1)-max(f0,f1);
            g[tree[x].fa][1]+=g0-f0;
        }
    }
}
int lastans;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs1(1,0);dfs2(1,0);
    tree[0].val.a[1][1]=tree[0].val.a[2][2]=0;
    tree[0].sum=tree[0].val;
    rt=dfs(1,0);
    while(m--){
        int x,y;scanf("%d%d",&x,&y);
        x^=lastans;
        update(x,y);
        lastans=-0x3f3f3f3f;
        for(int i=1;i<=2;i++)for(int j=1;j<=2;j++)lastans=max(lastans,tree[rt].sum.a[i][j]);
        printf("%d\n",lastans);
    }
    return 0;
}
posted @ 2023-05-02 15:41  gtm1514  阅读(37)  评论(0编辑  收藏  举报