动态 DP
带点权的树,每次修改一个点的权值,求树的最大权独立集。
\(1\le n,m \le 10^5\),点权的绝对值 \(\le 10^2\).
若不带修,先设 \(f_{u,1/0}\) 表示点 \(u\) 选与不选,子树 \(u\) 的最大权独立集。
发现一次修改更新整棵树的复杂度是 \(dep_u\) 的,在链上会被卡到 \(O(n)\).
考虑重剖,对于重链暴力更新。问题在于如何快速修改和查询链的 DP 值。
设 \(g_{u,1/0}\) 为所有取自己且轻儿子都不取,自己不取且轻儿子可取可不取的最大权独立集。
\(v\) 是 \(u\) 的重儿子。
构造 \(1\times 2\) 的矩阵
重新定义一个矩阵乘法:
若 \(C=A * B\),则
从重儿子 \(v\) 转移到 \(u\) 身上:
已知
得到
访问区间的查询是从链头到链尾的,需要维护的信息初始在链尾上,所以应该将答案矩阵放在后面:
线段树维护一段区间中矩阵的乘积。
-
对于一个点的 dp 值,需从该点查询至区间链尾,树剖时多记录一个 \(\text{end}\).
-
\(\text{modify}\) 在函数外记录 \(\text{val}\lbrack p \rbrack\) 表示节点的转移矩阵,避免递归常数过大。
-
\(\text{change}\) 里面要算出未修改个修改后的两个矩阵,然后再修改当前节点的 \(\text{val}\).
-
这里的 \(\text{query}\) 要换种写法不然会带进去一些奇怪的东西。
时间复杂度 \(O(n\log^2 n)\),加上矩乘的常数。
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
struct Mat{
int a[2][2];
Mat(){memset(a,-0x3f,sizeof(a));}
void clear(){memset(a,0,sizeof(a));}
Mat operator*(Mat x){
Mat c;
for(int i=0;i<2;i++)
for(int k=0;k<2;k++)
for(int j=0;j<2;j++)
c.a[i][j]=max(c.a[i][j],a[i][k]+x.a[k][j]);
return c;
}
};
int n,m,a[N];
vector<int>s[N];
int fa[N],siz[N],dep[N],son[N];
int top[N],id[N],dfn[N],ed[N],cnt;
int f[N][2];Mat val[N];
int L[N<<2],R[N<<2];Mat M[N<<2];
#define ls p<<1
#define rs p<<1|1
void update(int p){
M[p]=M[ls]*M[rs];
}
void build(int p,int l,int r){
L[p]=l,R[p]=r;
if(l==r){
M[p]=val[dfn[l]];
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
update(p);
}
void modify(int p,int x){
if(L[p]==R[p]){
M[p]=val[dfn[x]];
return;
}
int mid=(L[p]+R[p])>>1;
if(x<=mid)modify(ls,x);
else modify(rs,x);
update(p);
}
Mat query(int p,int l,int r){
if(L[p]==l&&R[p]==r)return M[p];
int mid=(L[p]+R[p])>>1;
if(r<=mid)return query(ls,l,r);
if(l>mid)return query(rs,l,r);
return query(ls,l,mid)*query(rs,mid+1,r);
}
void dfs1(int u){
siz[u]=1;
for(int v:s[u]){
if(dep[v])continue;
fa[v]=u,dep[v]=dep[u]+1;
dfs1(v),siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
void dfs2(int u,int t){
id[u]=++cnt,dfn[cnt]=u;
top[u]=t,ed[t]=max(ed[t],cnt);
f[u][0]=0,f[u][1]=a[u];
val[u].a[0][0]=val[u].a[0][1]=0;
val[u].a[1][0]=a[u];
if(son[u]){
dfs2(son[u],t);
f[u][0]+=max(f[son[u]][0],f[son[u]][1]);
f[u][1]+=f[son[u]][0];
}
for(int v:s[u]){
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
val[u].a[0][0]+=max(f[v][0],f[v][1]);
val[u].a[0][1]=val[u].a[0][0];
val[u].a[1][0]+=f[v][0];
}
}
void change(int u,int w){
val[u].a[1][0]+=w-a[u],a[u]=w;
Mat pre,cur;
while(u){
pre=query(1,id[top[u]],ed[top[u]]);
modify(1,id[u]);
cur=query(1,id[top[u]],ed[top[u]]);
u=fa[top[u]];
val[u].a[0][0]+=max(cur.a[0][0],cur.a[1][0])-max(pre.a[0][0],pre.a[1][0]);
val[u].a[0][1]=val[u].a[0][0];
val[u].a[1][0]+=cur.a[0][0]-pre.a[0][0];
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
s[u].push_back(v),s[v].push_back(u);
}
dep[1]=1,dfs1(1),dfs2(1,1);
build(1,1,n);
for(int u,w;m;m--){
u=read(),w=read();
change(u,w);
Mat ans=query(1,id[1],ed[1]);
printf("%d\n",max(ans.a[0][0],ans.a[1][0]));
}
return 0;
}
带点权的树,求它的最小权覆盖集。
每次给出 \(a,x,b,y\),\(x=1/0\) 代表 \(a\) 强制选/不选,\(y=1/0\) 代表 \(b\) 强制选/不选。
\(1\le n,m\le 10^5\),点权 \(\in \lbrack 1,10^5\rbrack\).
首先有 最小权覆盖集=全集-最大权独立集。
强制选点故在最大权独立集中不存在,设为 \(-\infty\),强制不选设为 \(+\infty\)。
不合法即当最小权覆盖集 \(\ge\infty\).
#include<bits/stdc++.h>
#define ll long long
#define N 100010
#define inf 1e12
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
struct Mat{
ll a[2][2];
Mat(){a[0][0]=a[0][1]=a[1][0]=a[1][1]=-inf;}
Mat operator*(Mat x){
Mat c;
for(int i=0;i<2;i++)
for(int k=0;k<2;k++)
for(int j=0;j<2;j++)
c.a[i][j]=max(c.a[i][j],a[i][k]+x.a[k][j]);
return c;
}
};
int n,m,type;ll A[N],sum;
vector<int>s[N];
int fa[N],siz[N],dep[N],son[N];
int top[N],id[N],dfn[N],ed[N],cnt;
ll f[N][2];Mat val[N];
int L[N<<2],R[N<<2];Mat M[N<<2];
#define ls p<<1
#define rs p<<1|1
void update(int p){
M[p]=M[ls]*M[rs];
}
void build(int p,int l,int r){
L[p]=l,R[p]=r;
if(l==r){
M[p]=val[dfn[l]];
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
update(p);
}
void modify(int p,int x){
if(L[p]==R[p]){
M[p]=val[dfn[x]];
return;
}
int mid=(L[p]+R[p])>>1;
if(x<=mid)modify(ls,x);
else modify(rs,x);
update(p);
}
Mat query(int p,int l,int r){
if(L[p]==l&&R[p]==r)return M[p];
int mid=(L[p]+R[p])>>1;
if(r<=mid)return query(ls,l,r);
if(l>mid)return query(rs,l,r);
return query(ls,l,mid)*query(rs,mid+1,r);
}
void dfs1(int u){
siz[u]=1;
for(int v:s[u]){
if(dep[v])continue;
fa[v]=u,dep[v]=dep[u]+1;
dfs1(v),siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
void dfs2(int u,int t){
id[u]=++cnt,dfn[cnt]=u;
top[u]=t,ed[t]=max(ed[t],cnt);
f[u][0]=0,f[u][1]=A[u];
val[u].a[0][0]=val[u].a[0][1]=0;
val[u].a[1][0]=A[u];
if(son[u]){
dfs2(son[u],t);
f[u][0]+=max(f[son[u]][0],f[son[u]][1]);
f[u][1]+=f[son[u]][0];
}
for(int v:s[u]){
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
val[u].a[0][0]+=max(f[v][0],f[v][1]);
val[u].a[0][1]=val[u].a[0][0];
val[u].a[1][0]+=f[v][0];
}
}
void change(int u,ll w){
val[u].a[1][0]+=w-A[u],A[u]=w;
Mat pre,cur;
while(u){
pre=query(1,id[top[u]],ed[top[u]]);
modify(1,id[u]);
cur=query(1,id[top[u]],ed[top[u]]);
u=fa[top[u]];
val[u].a[0][0]+=max(cur.a[0][0],cur.a[1][0])-max(pre.a[0][0],pre.a[1][0]);
val[u].a[0][1]=val[u].a[0][0];
val[u].a[1][0]+=cur.a[0][0]-pre.a[0][0];
}
}
int main(){
n=read(),m=read(),type=read();
for(int i=1;i<=n;i++)
A[i]=read(),sum+=A[i];
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
s[u].push_back(v),s[v].push_back(u);
}
dep[1]=1,dfs1(1),dfs2(1,1);
build(1,1,n);
for(int a,b,x,y;m;m--){
a=read(),x=read(),b=read(),y=read();
ll ta=A[a],tb=A[b];
change(a,!x?inf:-inf);
change(b,!y?inf:-inf);
Mat Ans=query(1,id[1],ed[1]);
ll ans=max(Ans.a[0][0],Ans.a[1][0]);
change(a,ta),change(b,tb);
if(!x)ans-=inf,ans+=A[a];
if(!y)ans-=inf,ans+=A[b];
if(sum-ans<inf)printf("%lld\n",sum-ans);
else printf("-1\n");
}
return 0;
}
草。没绷住。
一棵树,从 \(s\) 出发,不断走到距当前节点不超过 \(k\) 的点,问走到 \(t\),经过的点集 \(\{c\}\) 的最小总点权。
\(1\le n,Q\le 2\times 10^5\),\(1\le k\le 3\).
记 \(f_i\) 为链上第 \(i\) 个点的 DP 值。
定义矩阵乘法
\(k=1\):简单树上差分。
\(k=2\):容易发现 \(c\) 都在链上。
\(k=3\):最多向外走一步。
记 \(f_{i,j}\) 表示在离点 \(i\) 距离为 \(j\) 的点上的最小代价,且\(b_i=\min\limits_{\text{j是i的儿子}}v_j\).
这不是动态DP这是倍增。
记 \(D_{i,j},U_{i,j}\) 为 \(i\) 为终点,从上往下/从下往上 \(2^j\) 个点的转移矩阵。预处理时间复杂度 \(O(k^3n\log n)\).
询问拆成 \(s\rightarrow \text{lca}\),\(\text{lca}\),\(\text{lca}\rightarrow t\) 三部分,最后乘起来。
\(\text{lca}\) 可以走到父亲上,修改其转移矩阵中的 \(b_i\).
时间复杂度 \(O(k^3(n+q)\log n)\).
真的难写。
#include<bits/stdc++.h>
#define ll long long
#define N 200010
#define L 19
#define inf 1e18
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
struct Mat{
int r,c;ll a[3][3];
Mat(int _r=0,int _c=0):r(_r),c(_c){
for(int i=0;i<r;i++)
for(int j=0;j<c;j++)a[i][j]=inf;
}
void EMat(){
for(int i=0;i<r&&i<c;i++)
a[i][i]=0;
}
Mat operator*(Mat x){
Mat ret(r,x.c);
for(int i=0;i<r;i++)
for(int k=0;k<c;k++)
for(int j=0;j<x.c;j++)
ret.a[i][j]=min(ret.a[i][j],a[i][k]+x.a[k][j]);
return ret;
}
}f0(1,3),D[N][L],U[N][L];
int n,m,k;ll a[N],b[N];
vector<int>s[N];
int fa[N][L],dep[N];
Mat init(int u,ll t){
Mat c(3,3);
if(k==1)c.a[0][0]=a[u];
else if(k==2)c.a[0][1]=0,c.a[0][0]=c.a[1][0]=a[u];
else{
c.a[0][0]=c.a[1][0]=c.a[2][0]=a[u];
c.a[0][1]=c.a[1][2]=0,c.a[1][1]=min(b[u],t);
}
return c;
}
void dfs(int u,int f){
fa[u][0]=f,dep[u]=dep[f]+1,b[u]=1e18;
for(int v:s[u]){
if(v==f)continue;
dfs(v,u),b[u]=min(b[u],a[v]);
}
D[u][0]=U[u][0]=init(u,inf);
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
for(int i=L-1;i>=0;i--)
if((dep[u]-dep[v])>>i)u=fa[u][i];
if(u==v)return u;
for(int i=L-1;i>=0;i--)
if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
Mat QD(int u,int j){
Mat ret(3,3);ret.EMat();
for(int i=L-1;i>=0;i--)
if((j>>i)&1)ret=D[u][i]*ret,u=fa[u][i];
return ret;
}
Mat QU(int u,int j){
Mat ret(3,3);ret.EMat();
for(int i=L-1;i>=0;i--)
if((j>>i)&1)ret=ret*U[u][i],u=fa[u][i];
return ret;
}
ll query(int u,int v){
int Lca=lca(u,v);
Mat ans=f0*QU(u,dep[u]-dep[Lca])*init(Lca,a[fa[Lca][0]])*QD(v,dep[v]-dep[Lca]);
return min(ans.a[0][0]-a[v],min(ans.a[0][1],ans.a[0][2]))+a[v];
}
int main(){
n=read(),m=read(),k=read();
f0.a[0][k-1]=0,a[0]=inf;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
s[u].push_back(v),s[v].push_back(u);
}
dfs(1,0);D[0][0]=U[0][0]=Mat(3,3);
for(int j=1;j<L;j++){
for(int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
for(int i=1;i<=n;i++)
D[i][j]=D[fa[i][j-1]][j-1]*D[i][j-1];
for(int i=1;i<=n;i++)
U[i][j]=U[i][j-1]*U[fa[i][j-1]][j-1];
}
for(int u,v;m;m--){
u=read(),v=read();
printf("%lld\n",query(u,v));
}
return 0;
}