cunzai_zsy0531

关注我

常用算法学习笔记

Post time: 2021-05-18 16:48:40

整体二分

模板题:\(q\) 次查询区间第 \(k\) 小,带单点修改。\(n,q\leq 10^5\)

考虑如果只有一次询问,有一个做法是考虑在值域 \([L,R]\) 上二分答案 \(mid\),把所有 \(\leq mid\) 的数设为 \(1\),否则设为 \(0\),如果查询区间 \([l,r]\) 内的和 \(\geq k\),说明答案在 \([L,mid]\) 里,否则在 \([mid+1,R]\) 里。这样做的复杂度是 \(O(n\log v)\)

如果有 \(q\) 组询问,暴力做法就是对于每个询问都做一遍这个做法。但是这样显然复杂度爆炸,考虑合并这些询问。因为每一次求解都是在同一个值域上二分,不妨把所有询问一起二分答案 \(mid\)。把所有答案在 \([L,mid]\) 的询问放到左边递归,答案在 \([mid+1,R]\) 的询问放在右边递归,当 \(L=R\) 或者区间内没有询问时终止。这样,只需要快速统计区间内的和了。这显然可以用树状数组实现,所以总的复杂度是 \(O(n\log n\log v)\)

有修改操作怎么办?考虑把修改操作拆成删除和插入,和询问一起按照时间顺序做上边的操作,如果这个操作是加入或删除,则在树状数组里 \(+1\) 或者 \(-1\)。这样就可以直接用上面那个做法做了,复杂度不变。

模板P2617

点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+13;
struct Node{int op,x,y,z;}q[N<<2],lq[N<<2],rq[N<<2];
int n,m,ans[N],a[N];
struct BIT{
	int t[N];
	inline int lowbit(int x){return x&-x;}
	inline void add(int x,int k){for(;x<=n;x+=lowbit(x))t[x]+=k;}
	inline int sum(int x){int res=0;for(;x;x-=lowbit(x))res+=t[x];return res;}
}T;
void solve(int L,int R,int l,int r){
	if(l>r) return;
	if(L==R){
		for(int i=l;i<=r;++i)
			if(q[i].op>0) ans[q[i].op]=L;
		return;
	}
	int mid=(L+R)>>1;
	int lt=0,rt=0;
	for(int i=l;i<=r;++i){
		if(q[i].op<=0){
			if(q[i].y<=mid) T.add(q[i].x,q[i].op?-1:1),lq[++lt]=q[i];
			else rq[++rt]=q[i];
		}
		else{
			int cnt=T.sum(q[i].y)-T.sum(q[i].x-1);
			if(cnt>=q[i].z) lq[++lt]=q[i];
			else rq[++rt]=q[i],rq[rt].z-=cnt;
		}
	}
	for(int i=l;i<=r;++i)
		if(q[i].op<=0&&q[i].y<=mid) T.add(q[i].x,q[i].op?1:-1);
	for(int i=1;i<=lt;++i) q[l+i-1]=lq[i];
	for(int i=1;i<=rt;++i) q[l+lt+i-1]=rq[i];
	solve(L,mid,l,l+lt-1);
	solve(mid+1,R,l+lt,r);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),q[i].x=i,q[i].y=a[i];
	int cnt=n;
	for(int i=1;i<=m;++i){
		char in;cin>>in;
		if(in=='Q') ++cnt,q[cnt].op=i,scanf("%d%d%d",&q[cnt].x,&q[cnt].y,&q[cnt].z);
		else{
			int xx,yy;scanf("%d%d",&xx,&yy);
			q[++cnt].x=xx,q[cnt].y=yy;
			q[++cnt].x=xx,q[cnt].y=a[xx];
			a[xx]=yy,q[cnt].op=-1;
		}
	}
	memset(ans,-1,sizeof ans);
	solve(0,1e9,1,cnt);
	for(int i=1;i<=m;++i)
		if(ans[i]!=-1) printf("%d\n",ans[i]);
	return 0;
}

动态dp(动态树分治)

一般是对于一些比较简单的树形dp问题,加上了单点修改的操作。模板:树上最大权独立集,单点修改,\(n,q\leq 10^5\)

首先考虑没有修改的情况,设 \(f_{i,0}\) 表示不选 \(i\) 号点子树内的最大答案,\(f_{i,1}\) 表示选 \(i\) 号点(儿子都不选)的最大答案,写出dp式子

\[\begin{aligned} f_{u,0}&=\sum_{v\in son(u)}\max(f_{v,0},f_{v,1})\\ f_{u,1}&=\sum_{v\in son(u)} f_{v,0} \end{aligned} \]

当进行一次单点修改的时候,事实上只可能会修改从修改点到根的路径上的点的dp值。若想动态维护,那么可以很自然的想到进行重链剖分,这样每个点到根的路径上最多只会有 \(O(\log n)\) 条重链。

如何在每条重链上动态维护 dp 值呢?首先引入一个广义矩阵乘法 \(A* B=C\),其中

\[C_{i,j}=\max_k(A_{i,k}+B_{k,j}) \]

也就是将原来矩阵乘法的乘变加、加变 \(\max\),可以证明这样的矩阵乘法也满足结合律。

构造一个答案矩阵 \(\begin{bmatrix}f_{i,0}\\f_{i,1}\end{bmatrix}\),考虑转移:由于这里出现了轻重链以及轻重儿子的区分,所以需要重新设计dp状态: \(g_{i,0}\) 表示不选 \(i\),只考虑轻儿子时的答案;\(g_{i,1}\) 表示选 \(i\),只考虑轻儿子时的答案。设 \(u\) 的重儿子为 \(s[u]\),则

\[\begin{aligned} f_{u,0}&=g_{u,0}+\max(f_{s[u],0},f_{s[u],1})\\ f_{u,1}&=g_{u,1}+f_{s[u],0} \end{aligned} \]

把它化成广义矩阵乘法的形式,即

\[\begin{aligned} f_{u,0}&=\max(g_{u,0}+f_{s[u],0},g_{u,0}+f_{s[u],1})\\ f_{u,1}&=\max(g_{u,1}+f_{s[u],0},-\infty) \end{aligned} \]

化为矩阵形式,即

\[\begin{bmatrix} g_{u,0} & g_{u,0}\\ g_{u,1} & -\infty \end{bmatrix} * \begin{bmatrix}f_{s[u],0}\\f_{s[u],1}\end{bmatrix} = \begin{bmatrix}f_{u,0}\\f_{u,1}\end{bmatrix} \]

每一段重链的结尾都一定是叶子结点,在这个结点存下 \(f\) 答案数组,然后就可以一步一步向上更新,利用线段树维护即可。单点修改的时候只需要修改每条重链的链尾,通过链头的 \(g\) 值去更新上一个链链尾的 \(f\) 值即可。最后的查询直接查整条 \(1\) 的重链即可。

模板P4719

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline int max(const int &a,const int &b){return a>b?a:b;}
const int N=1e5+13,INF=0x3f3f3f3f;
struct Matrix{
	int d[3][3];
	Matrix(){memset(d,0,sizeof d);}
	Matrix operator *(const Matrix &A)const{
		Matrix Ans;
		for(int i=1;i<=2;++i)
		for(int j=1;j<=2;++j)
		for(int k=1;k<=2;++k)
			Ans.d[i][j]=max(Ans.d[i][j],d[i][k]+A.d[k][j]);
		return Ans;
	}
}g[N];
struct Edge{int v,nxt;}e[N<<1];
int n,m,a[N],h[N],tot;
int fa[N],siz[N],dep[N],f[N][2],son[N],top[N],id[N],b[N],dfs_clock,ed[N];
inline void add_edge(int u,int v){e[++tot]=(Edge){v,h[u]};h[u]=tot;}
void dfs1(int u,int father,int deep){
	fa[u]=father,siz[u]=1,dep[u]=deep;
	f[u][0]=0,f[u][1]=a[u];
	int maxson=0;
	for(int i=h[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==father) continue;
		dfs1(v,u,deep+1);siz[u]+=siz[v];
		if(siz[v]>maxson) maxson=siz[v],son[u]=v;
		f[u][0]+=max(f[v][0],f[v][1]),f[u][1]+=f[v][0];
	}
}
void dfs2(int u,int topf){
	top[u]=topf,id[u]=++dfs_clock,b[dfs_clock]=u;
	g[u].d[2][1]=a[u],g[u].d[2][2]=-INF;
	ed[topf]=dfs_clock;
	if(!son[u]) return;
	dfs2(son[u],topf);
	for(int i=h[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
		g[u].d[1][1]+=max(f[v][0],f[v][1]);
		g[u].d[2][1]+=f[v][0];
	}
	g[u].d[1][2]=g[u].d[1][1];
}
struct SegTree{int l,r;Matrix x;}t[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((t[p].l+t[p].r)>>1)
inline void refresh(int p){t[p].x=t[ls].x*t[rs].x;}
void build(int p,int l,int r){
	t[p].l=l,t[p].r=r;
	if(l==r) return void(t[p].x=g[b[l]]);
	build(ls,l,mid),build(rs,mid+1,r);
	refresh(p);
}
void update(int p,int x){
	if(t[p].l==t[p].r) return void(t[p].x=g[b[x]]);
	update(x<=mid?ls:rs,x);
	refresh(p);
}
Matrix query(int p,int l,int r){
	if(l<=t[p].l&&t[p].r<=r) return t[p].x;
	if(r<=mid) return query(ls,l,r);
	if(l>mid) return query(rs,l,r);
	return query(ls,l,r)*query(rs,l,r);
}
inline void t_update(int u,int w){
	g[u].d[2][1]+=w-a[u];a[u]=w;
	while(u){
		Matrix las=query(1,id[top[u]],ed[top[u]]);
		update(1,id[u]);
		Matrix now=query(1,id[top[u]],ed[top[u]]);
		u=fa[top[u]];
		g[u].d[1][1]+=max(now.d[1][1],now.d[2][1])-max(las.d[1][1],las.d[2][1]);
		g[u].d[1][2]=g[u].d[1][1];
		g[u].d[2][1]+=now.d[1][1]-las.d[1][1];
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),add_edge(u,v),add_edge(v,u);
	dfs1(1,0,0);dfs2(1,1);build(1,1,n);
	while(m--){
		int x,y;scanf("%d%d",&x,&y);
		t_update(x,y);
		Matrix ans=query(1,1,ed[1]);
		printf("%d\n",max(ans.d[1][1],ans.d[2][1]));
	}
	return 0;
}
posted @ 2022-05-03 16:42  cunzai_zsy0531  阅读(37)  评论(0编辑  收藏  举报