常用算法学习笔记
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\)。这样就可以直接用上面那个做法做了,复杂度不变。
点击查看代码
#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式子
当进行一次单点修改的时候,事实上只可能会修改从修改点到根的路径上的点的dp值。若想动态维护,那么可以很自然的想到进行重链剖分,这样每个点到根的路径上最多只会有 \(O(\log n)\) 条重链。
如何在每条重链上动态维护 dp 值呢?首先引入一个广义矩阵乘法 \(A* B=C\),其中
也就是将原来矩阵乘法的乘变加、加变 \(\max\),可以证明这样的矩阵乘法也满足结合律。
构造一个答案矩阵 \(\begin{bmatrix}f_{i,0}\\f_{i,1}\end{bmatrix}\),考虑转移:由于这里出现了轻重链以及轻重儿子的区分,所以需要重新设计dp状态: \(g_{i,0}\) 表示不选 \(i\),只考虑轻儿子时的答案;\(g_{i,1}\) 表示选 \(i\),只考虑轻儿子时的答案。设 \(u\) 的重儿子为 \(s[u]\),则
把它化成广义矩阵乘法的形式,即
化为矩阵形式,即
每一段重链的结尾都一定是叶子结点,在这个结点存下 \(f\) 答案数组,然后就可以一步一步向上更新,利用线段树维护即可。单点修改的时候只需要修改每条重链的链尾,通过链头的 \(g\) 值去更新上一个链链尾的 \(f\) 值即可。最后的查询直接查整条 \(1\) 的重链即可。
点击查看代码
#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;
}