洪水 bzoj-4712

题目大意:给定一棵$n$个节点的有根树。每次询问以一棵节点为根的子树内,选取一些节点使得这个被询问的节点包含的叶子节点都有一个父亲被选中,求最小权值。支持单点修改。

注释:$1\le n\le 2\cdot 10^5$。保证任意时刻所有节点的权值为正整数。


想法:显然这是一道动态$dp$的题。

如果没有单点修改操作,我们直接树形$dp$:

  状态:$f_i$表示以$i$为根的答案。

  转移:$f_i=max(a_i,\sum\limits f_j)$。

现在有了单点修改操作,我们像正常的动态树形dp一样树剖。

维护$g_i=\sum\limits f_j$其中,$j$是$i$的虚儿子。

这样的话$f_i=max(a_i,g_i+f_{i+1})$因为重儿子在树剖中紧挨着父亲。

进而我们考虑一条重链上:因为每个节点的权值都是正的,所以一个叶子节点只有一个祖先会被选中。

我们考虑当前节点所在重链的链底(显然是一个叶子),重链上一定有且仅有一个节点被选中,不妨设为$x$,那么代价就是$a_x+\sum\limits_{j=i}^{x-1} g_j$。

就是最小前缀和的形式。

所以询问就是询问一条重链上的最小前缀和。

接下来考虑单点修改:

显然单点修改不会影响重链上的$g$值,只需要在线段树上单点修改即可。

它只会影响每个重链的父亲。那么我们求出了当前重链链头的$f$值,紧接着更新重链链头的父亲的$g$,以此类推。

用线段树维护最小前缀和,同时需要维护区间和。

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200010
#define lson l,mid,x<<1
#define rson mid+1,r,x<<1|1
using namespace std; typedef long long ll;
struct Node
{
ll sum,ls;
Node() {}
Node(ll g,ll v){sum=g; ls=v;}
inline Node operator +(const Node &a) const
{
return Node(sum+a.sum,min(sum+a.ls,ls));
}
}a[N<<2],w[N];
int head[N],to[N<<1],nxt[N<<1],cnt,fa[N],size[N],top[N],down[N],dic[N],tot,n;
ll v[N],f[N],g[N];
char str[5];
inline void add(int x,int y) {to[++cnt]=y; nxt[cnt]=head[x]; head[x]=cnt;}
void dfs1(int x)
{
size[x]=1;
for(int i=head[x];i;i=nxt[i]) if(to[i]!=fa[x])
fa[to[i]]=x,dfs1(to[i]),size[x]+=size[to[i]];
}
void dfs2(int x,int c)
{
int k=0;
top[x]=c,dic[x]=++tot,down[x]=x,f[x]=v[x];
for(int i=head[x];i;i=nxt[i]) if(to[i]!=fa[x]&&size[to[i]]>size[k])
{
k=to[i];
}
if(k)
{
dfs2(k,c),down[x]=down[k];
for(int i=head[x];i;i=nxt[i])
if(to[i]!=fa[x]&&to[i]!=k)
dfs2(to[i],to[i]),g[x]+=f[to[i]];
f[x]=min(f[x],f[k]+g[x]);
}
w[dic[x]]=Node(g[x],v[x]);
}
inline void pushup(int x) {a[x]=a[x<<1]+a[x<<1|1];}
void build(int l,int r,int x)
{
if(l==r)
{
a[x]=w[l];
return;
}
int mid=(l+r)>>1;
build(lson); build(rson);
pushup(x);
}
void updatev(int p,ll v,int l,int r,int x)
{
if(l==r)
{
a[x].ls+=v;
return;
}
int mid=(l+r)>>1;
if(p<=mid) updatev(p,v,lson);
else updatev(p,v,rson);
pushup(x);
}
void updateg(int p,ll g,int l,int r,int x)
{
if(l==r)
{
a[x].sum+=g;
return;
}
int mid=(l+r)>>1;
if(p<=mid) updateg(p,g,lson);
else updateg(p,g,rson);
pushup(x);
}
Node query(int b,int e,int l,int r,int x)
{
if(b<=l&&r<=e) return a[x];
int mid=(l+r)>>1;
if(e<=mid) return query(b,e,lson);
else if(b>mid) return query(b,e,rson);
else return query(b,e,lson)+query(b,e,rson);
}
inline void modify(int x,ll v)
{
int y=x;
ll t;
while(x)
{
t=query(dic[top[x]],dic[down[x]],1,n,1).ls;
if(x==y) updatev(dic[x],v,1,n,1);
else updateg(dic[x],v,1,n,1);
v=query(dic[top[x]],dic[down[x]],1,n,1).ls-t,x=fa[top[x]];
}
}
int main()
{
int m,x,y;
ll z;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&v[i]);
for(int i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
dfs1(1),dfs2(1,1);
build(1,n,1);
scanf("%d",&m);
while(m--)
{
scanf("%s%d",str,&x);
if(str[0]=='C')scanf("%lld",&z),modify(x,z);
else printf("%lld\n",query(dic[x],dic[down[x]],1,n,1).ls);
}
return 0;
}

小结:无。

最新文章

  1. Ubuntu实现树莓派交叉编译
  2. patch 打补丁,和diff 生成制作补丁
  3. SSAS 部署失败 总结
  4. Js练笔——用循环和递归实现追踪对象深度(循环引用关系不考虑)
  5. PHP入门part1
  6. Stockbroker Grapevine(floyd)
  7. Windows Socket网络编程-2016.01.07
  8. Web前端设计:Html强制不换行&lt;nobr&gt;标签用法代码示例
  9. no identities are available for signing
  10. IOS计划 分析
  11. UVA-11134-Fabled Rooks (结构排序+贪婪)
  12. MFC HTTP
  13. php fsockopen
  14. 【VB超简单入门】六、基本数据类型
  15. 在Python中使用SMTP发送电子邮件
  16. Spring Boot中的initializers的作用分析
  17. 11:django 模板 内建标签
  18. 与TIME_WAIT相关的几个内核参数修改测试讨论结论
  19. go 多态
  20. Linux Command : top

热门文章

  1. poj1930 Dead Fraction
  2. Spark学习之Spark SQL(8)
  3. java urlEncode 和urlDecode的用法
  4. JAVA 学习笔记 - 反射机制
  5. C/C++ 各进制赋值、int/char转换、sscanf/sprintf、位操作运算
  6. python 需求分析
  7. MIPS的寄存器、指令和寻址方式的分类
  8. bzero - 向字符串写入零
  9. 共享win7ip,虚拟机nat模式连接,电脑重启之后,无法连接
  10. width:100px; min-width:100% 解释:宽度大于100px 就是100% 小于100px 就是100像素