【bzoj4712】洪水
Description
给你一棵树,节点从\(1\)到\(n\)编号,每个节点有一个权值,有若干次操作,操作有以下两种:
\((C,x,delta)\):将编号为\(x\)的点的权值改为\(delta\)
\((Q,x)\):询问将\(x\)号节点为根的子树中的所有叶子结点与子树外的其他所有叶子节点分离的最小代价,分离可以通过删除节点实现,删除一个节点的对应代价为该点的权值
数据范围:\(n<=200000\),任意\(delta\)均为非负数,答案在long long范围内
Solution
这题貌似是。。经典的动态dp问题,维护重链信息\(f\)和轻儿子信息之和\(g\)之后可以用树剖解决
我们先考虑没有修改操作的情况,那就是直接dp
记\(f[x]\)表示以\(x\)为根的子树的答案,\(val[x]\)表示\(x\)点的权值,那么有:
如果要修改的话我们考虑树剖,再定义一个数组\(g\),\(g[x]\)表示\(x\)的轻儿子的\(f\)值之和,那么就有:
其中\(son[x]\)表示的是\(x\)的重儿子
这个时候就有好几种不同的做法了,反正就是维护这两个值就好了,接下来讲的是一种通过维护一个类似转移矩阵一样的东西来用树剖维护的方法
我们考虑定义一种新的矩阵乘法(额好像也不能叫做矩阵乘法反正就是一种新的运算):
其中\(A,B,C\)都是矩阵,其实这种运算就是将原来的矩阵乘法的乘法改成了取最小值
这个运算是满足结合律的,具体证明的话就是我们可以将一次这样的运算理解成求最短路,然后连续两次这样的运算的话就是相当于你有三排点,要求最左边到最右边的最短路,显然你可以先求出左边两排的最短路,或者先求出右边两排的最短路,最后得到的结果是一样的
定义完这样的运算有什么用呢?我们可以考虑将\(f[x]\)的求值过程用这种运算来实现,为了方便表示我们将这种运算的符号约定为\(*\):
具体的话就是。。大力展开一下就好了
然后我们可以发现\(\begin{bmatrix}f[son[x]]\\0\end{bmatrix}\)是可以写成\(\begin{bmatrix}g[son[son[x]]]&val[son[son[x]]]\\0&0\end{bmatrix}\)的,一路写下去就会发现其实就是\(x\)所在的整条重链中,从\(x\)这个位置的矩阵一路连续运算一直到重链底就好了
注意到这个剖完之后就是一段连续的区间,然后这样我们就可以放心用线段树来维护区间的矩阵的连续运算得到的结果,然后其他的就是跟树剖差不多的操作了
其实还是有(hen)点(da)区别的,具体一点就是
1、查询
与普通树剖不同的是这样我们维护的是。。某个位置到链底的权值运算和,所以。。我们需要记录的是每个点所在的链底在哪里,然后查询的时候并不需要往上跳直接查就好了
2、修改
修改的话因为这里会影响到祖先的\(g\)值,所以我们还是要常规操作一路跳上去,不过不同的是,这里修改直接暴力通过线段树区间查询求得\(f[top[x]\)然后在\(g[pre[top[x]]]\)里面减去(\(pre[x]\)表示\(x\)在原树上的直接祖先,\(top[x]\)表示\(x\)所在的重链头),然后更新线段树中的信息,然后再将新的值加回\(g[pre[top[x]]]\)中
然后就十分愉悦\(O(nlog^2n)\)做完啦,这个做法有一个好处就是。。就算没有保证修改的\(delta\)非负也可以直接做,以及重载运算符之后线段树写起来很爽qwq
以及洛谷上面貌似有碾爆LCT和树剖的一个\(log\)做法qwq(orzlyy!!)然而。。我太懒了并没有写qwq
代码大概长这个样子
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=200010,SEG=N*4;
const ll inf=1LL<<60;
struct xxx{
int y,nxt;
}a[N*2];
int h[N],lis[N],dfn[N],ed[N],pre[N],sz[N];//ed=the end of the chain
ll f[N],g[N],val[N];
int son[N],top[N];
int n,m,tot,dfn_t;
ll ans;
struct Mtrix{/*{{{*/
ll a[2][2];
int n;
void init(int _n){
n=_n;
for (int i=0;i<n;++i)
for (int j=0;j<n;++j) a[i][j]=inf;
}
friend Mtrix operator * (Mtrix x,Mtrix y){
Mtrix ret; ret.n=x.n;
for (int i=0;i<ret.n;++i)
for (int j=0;j<ret.n;++j){
ret.a[i][j]=inf;
for (int k=0;k<ret.n;++k)
if (x.a[i][k]!=inf&&y.a[k][j]!=inf)
ret.a[i][j]=min(ret.a[i][j],x.a[i][k]+y.a[k][j]);
}
return ret;
}
};/*}}}*/
namespace Seg{/*{{{*/
int ch[SEG][2];
Mtrix info[SEG];
int n,tot;
void pushup(int x){info[x]=info[ch[x][0]]*info[ch[x][1]];}
void _build(int x,int l,int r){
info[x].init(2);
if (l==r){
info[x].a[0][0]=g[lis[l]];
info[x].a[0][1]=val[lis[l]];
info[x].a[1][0]=info[x].a[1][1]=0;
return;
}
int mid=l+r>>1;
ch[x][0]=++tot; _build(ch[x][0],l,mid);
ch[x][1]=++tot; _build(ch[x][1],mid+1,r);
pushup(x);
}
void build(int _n){n=_n;tot=1;_build(1,1,n);}
void _update(int x,int d,int lx,int rx){
if (lx==rx){
info[x].a[0][0]=g[lis[lx]];
info[x].a[0][1]=val[lis[lx]];
info[x].a[1][0]=info[x].a[1][1]=0;
return;
}
int mid=lx+rx>>1;
if (d<=mid) _update(ch[x][0],d,lx,mid);
else _update(ch[x][1],d,mid+1,rx);
pushup(x);
}
void update(int d){_update(1,d,1,n);}
Mtrix _query(int x,int l,int r,int lx,int rx){
if (l<=lx&&rx<=r) return info[x];
int mid=lx+rx>>1;
if (r<=mid) return _query(ch[x][0],l,r,lx,mid);
else if (l>mid) return _query(ch[x][1],l,r,mid+1,rx);
else{
return _query(ch[x][0],l,mid,lx,mid)*_query(ch[x][1],mid+1,r,mid+1,rx);
}
}
Mtrix query(int l,int r){return _query(1,l,r,1,n);}
}/*}}}*/
void add(int x,int y){a[++tot].y=y; a[tot].nxt=h[x]; h[x]=tot;}
void dfs(int fa,int x){
int u;
sz[x]=1; son[x]=0; pre[x]=fa;
for (int i=h[x];i!=-1;i=a[i].nxt){
u=a[i].y;
if (u==fa) continue;
dfs(x,u);
sz[x]+=sz[u];
if (sz[u]>sz[son[x]]) son[x]=u;
}
}
void dfs1(int fa,int x){
int u,cntson=0;
dfn[x]=ed[x]=++dfn_t; lis[dfn_t]=x;
if (son[x]){
top[son[x]]=top[x];
dfs1(x,son[x]);
ed[x]=ed[son[x]];
f[x]+=f[son[x]];
++cntson;
}
for (int i=h[x];i!=-1;i=a[i].nxt){
u=a[i].y;
if (u==fa||u==son[x]) continue;
top[u]=u;
dfs1(x,u);
g[x]+=f[u];
++cntson;
}
if (cntson==0)
g[x]=inf,f[x]=val[x];
else
f[x]=min(val[x],f[x]+g[x]);
}
ll query(int x){
Mtrix tmp=Seg::query(dfn[x],ed[x]);
return min(tmp.a[0][0],tmp.a[0][1]);
}
void update(int x,int delta){
ll tmp;
val[x]+=delta;
while (top[x]){
tmp=query(top[x]);
if (pre[top[x]])
g[pre[top[x]]]-=tmp;
Seg::update(dfn[x]);
tmp=query(top[x]);
if (pre[top[x]])
g[pre[top[x]]]+=tmp;
x=pre[top[x]];
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
char op[5];
int x,y;
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%lld",val+i);
memset(h,-1,sizeof(h));
tot=0;
for (int i=1;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
dfs(0,1);
top[1]=1; dfn_t=0;
dfs1(0,1);
Seg::build(dfn_t);
scanf("%d",&m);
for (int i=1;i<=m;++i){
scanf("%s",&op);
if (op[0]=='C'){
scanf("%d%d",&x,&y);
update(x,y);
}
else{
scanf("%d",&x);
ans=query(x);
printf("%lld\n",ans);
}
}
}