【题解】【BZOJ】BZOJ4712 洪水
BZOJ4712 洪水
BZOJ4712 洪水
1 题外话
鸽了好久
2 sol
首先考虑没有修改的情况,题目变成一个简单的dp
设\(f_i\) 表示从\(i\) 出发走不到所有其叶子节点的最小代价
则\(f_i=min(V_i,\sum_{t}f_t)\) 其中\(V_i\) 为删去\(i\) 点的权值,\(t\) 是\(i\) 的儿子
对树进行重链剖分,设\(g_i\) 表示\(i\) 的所有轻儿子的\(f\) 的和
则有\(f_i=min(V_i,g_i+f_son)\) ,其中\(son\) 表示\(i\) 的重儿子
将矩阵乘法写成min和+的形式
就有\(\begin{bmatrix}0&f_h\end{bmatrix}\times\begin{bmatrix}0&V_i\\\infty&g[i]\end{bmatrix}=\begin{bmatrix}0&f_i\end{bmatrix}\)
直接用树剖ddp即可
3 code
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define int long long const int N=200010; const int inf=2e9; inline void read(int &x) { x=0; int f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if (ch=='-') { f=-1; } ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } x*=f; } struct note { int t; int next; }; int n,m; int cnt; int head[N]; note e[N<<1]; inline void add(int x,int y) { e[++cnt].t=y; e[cnt].next=head[x]; head[x]=cnt; } struct mat { int a[2][2]; mat(int ax=inf,int b=inf,int c=inf,int d=inf) { a[0][0]=ax; a[0][1]=b; a[1][0]=c; a[1][1]=d; } inline void init_min() { for(int i=0;i<2;i++) { a[i][i]=0; } } }; inline mat operator * (const mat &x,const mat &y) { mat res; for(int i=0;i<2;i++) { for(int k=0;k<2;k++) { for(int j=0;j<2;j++) { res.a[i][j]=min(res.a[i][j],x.a[i][k]+y.a[k][j]); } } } return res; } int val[N]; namespace HLD { int tot; int dep[N],fa[N],siz[N],son[N]; int dfn[N],idfn[N],top[N],bot[N]; void dfs1(int p,int fat) { dep[p]=dep[fat]+1; fa[p]=fat; siz[p]=1; int max_siz=-1; for(int i=head[p];i+1;i=e[i].next) { int t=e[i].t; if (t==fat) { continue; } dfs1(t,p); siz[p]+=siz[t]; if (siz[t]>max_siz) { max_siz=siz[t]; son[p]=t; } } } void dfs2(int p,int top_p) { top[p]=top_p; dfn[p]=++tot; idfn[tot]=p; bot[p]=p; if (son[p]) { dfs2(son[p],top_p); bot[p]=bot[son[p]]; } for(int i=head[p];i+1;i=e[i].next) { int t=e[i].t; if (t==son[p]||t==fa[p]) { continue; } dfs2(t,t); } } mat t[N<<2],tmp[N]; void build(int p,int l,int r) { if (l==r) { t[p]=tmp[idfn[l]]; return; } int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); t[p]=t[p<<1|1]*t[p<<1]; } void modify(int p,int l,int r,int x) { if (l==r) { t[p]=tmp[idfn[l]]; return; } int mid=(l+r)>>1; if (x<=mid) { modify(p<<1,l,mid,x); } else { modify(p<<1|1, mid+1, r, x); } t[p]=t[p<<1|1]*t[p<<1]; } mat query(int p,int l,int r,int L,int R) { if (l==L&&r==R) { return t[p]; } int mid=(l+r)>>1; if (R<=mid) { return query(p<<1,l,mid,L,R); } if (L>mid) { return query(p<<1|1,mid+1,r,L,R); } return query(p<<1|1,mid+1,r,mid+1,R)*query(p<<1,l,mid,L,mid); } int f[N],g[N]; void pre_dp(int p) { f[p]=val[p]; if (son[p]) { pre_dp(son[p]); f[p]=min(f[p],f[son[p]]); } else { g[p]=inf; } for(int i=head[p];i+1;i=e[i].next) { int t=e[i].t; if (t==fa[p]||t==son[p]) { continue; } pre_dp(t); g[p]+=f[t]; } f[p]=min(f[p]+g[p],val[p]); tmp[p]=mat(0,val[p],inf,g[p]); } inline int get(int p) { return query(1, 1, n, dfn[p], dfn[bot[p]]).a[0][1]; } inline void mdf(int p) { while(p) { tmp[p]=mat(0,val[p],inf,g[p]); modify(1,1,n,dfn[p]); p=top[p]; if (p==1) { break; } g[fa[p]]-=f[p]; f[p]=get(p); g[fa[p]]+=f[p]; p=fa[p]; } } } signed main() { memset(head,-1,sizeof(head)); read(n); for(int i=1;i<=n;i++) { read(val[i]); } for(int i=1;i<n;i++) { int x,y; read(x),read(y); add(x,y); add(y,x); } HLD::dfs1(1,0); HLD::dfs2(1,1); HLD::pre_dp(1); HLD::build(1,1,n); read(m); for(int i=1;i<=m;i++) { char ch=getchar(); while(ch!='Q'&&ch!='C') { ch=getchar(); } int x; read(x); if (ch=='Q') { printf("%lld\n",HLD::get(x)); } else { int v; read(v); val[x]+=v; HLD::mdf(x); } } return 0; }
4 注意
矩阵乘法没有交换律(调了半个小时的罪魁祸首)
代码中线段树的询问写法和平常写的效果相同但不用多写一个单位矩阵(我不会找这种乘法定义下的单位矩阵)
记得开long long
Created: 2021-07-15 周四 15:23