[bzoj4712] 洪水 [树链剖分+线段树+dp]

题面

传送门

思路

DP方程

首先,这题如果没有修改操作就是sb题,dp方程如下

$dp[u]=max(v[u],max(dp[v]))$,其中$v$是$u$的儿子

我们令$g[u]=max(dp[v])$

修改?

我们发现,本题中所有的修改都是非负的

也就是说,每一次修改结束以后,$dp[u]$的值只增不减

同时,修改$u$位置的$v[u]$值,只会影响到$u$到根的这一条链上的$dp$值

我们考虑修改后,这条链上的每个点的$dp[u]$值的增量,令这个增量为$delta[u]$

那么显然当$delta[u]=0$时,$u$上面的所有$delta$都是0了

因此我们只需要考虑中间这一段$delta$不为0的部分,我们令这个部分为$[u,t]$

影响?

首先对于被修改的点$u$,如果本来就是$g[u]<v[u]$,那么$delta[u]=0$,不需要任何操作

否则,$delta[u]=min(v[u]+change,g[u])-v[u]$

然后,对于所有$(u,t]$中的点,它们的$g$会加上它们儿子那一层的点的$delta$

也就是说,后面这个过程中$v[x]$不受影响,所以$delta$不为0当且仅当$g[x]<v[x]$,并且这个过程中的$delta$是单调递减的

那么,一个单调递减的$delta$序列,能让你想到什么?

当然是二分查找啦!

二分

我们尝试二分得到第一个$delta[x]=0$的$x$

但是如果直接暴力算$delta$的话肯定就T飞了

我们需要一个优美一些的做法

我们考虑开一棵线段树来维护这个$delta$,同时线段树外面套一个树链剖分

显然我们只维护$delta$是无法维护的,我们还需要维护$g$的值,它的作用是询问的时候输出答案

发现这个$g$只需要叶子节点的值,所以我们可以非常方便的把开出来的$g$数组的非叶子部分作为$lazy$标记来使用

维护$delta$的时候,我们换一个方式表达它:维护$v[u]-g[u]$,这样既可以方便地区间修改,又可以当成查看$delta$是否在当前节点变成0的一部分信息

那么我们直接在线段树上二分,当前点为$u$

首先把$v[u]$修改,体现在线段树上,然后找到当前点到根的链上的$delta$0点,然后它下面的所有数的$g$值加上$delta[u]$,

二分的右端点,我们考虑它的$delta$,并以这个值为新的$delta$,重复上面的二分过程(注意不用修改$v[u]$)

查询的时候就是用记录好的$v[u]$和用线段树中维护的$g[u]$求最小值输出

Code

代码里面的s等价于上述的g

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cassert>
#define ll long long
using namespace std;
inline ll read(){
????ll re=0,flag=1;char ch=getchar();
????while(ch>'9'||ch<'0'){
????????if(ch=='-') flag=-1;
????????ch=getchar();
????}
????while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
????return re*flag;
}
int n,fa[200010],first[200010],dep[200010],siz[200010],son[200010],top[200010];
ll w[200010],s[200010],dp[200010];
int p[200010],back[200010],clk;
ll a[1000010],add[1000010];
struct edge{
????int to,next;
}e[400010];int cnte=0;
inline void adde(int u,int v){
????e[++cnte]=(edge){v,first[u]};first[u]=cnte;
????e[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
void dfs(int u,int f){
????int i,v;dep[u]=dep[f]+1;siz[u]=1;fa[u]=f;dp[u]=w[u];
????for(i=first[u];~i;i=e[i].next){
????????v=e[i].to;if(v==f) continue;
????????dfs(v,u);
????????siz[u]+=siz[v];s[u]+=dp[v];
????????if(siz[son[u]]<=siz[v]) son[u]=v;
????}
????if(siz[u]==1) s[u]=1e15;
????dp[u]=min(dp[u],s[u]);
}
void dfs2(int u,int t){
????int i,v;
????top[u]=t;
????clk++;p[u]=clk;back[clk]=u;
????if(son[u]) dfs2(son[u],t);
????for(i=first[u];~i;i=e[i].next){
????????v=e[i].to;if(v==fa[u]||v==son[u]) continue;
????????dfs2(v,v);
????}
}
void update(int num){
????a[num]=min(a[num<<1],a[num<<1|1]);
}
void push(int l,int r,int num){
????if(l==r||!add[num]) return;
????add[num<<1]+=add[num];add[num<<1|1]+=add[num];
????a[num<<1]-=add[num];a[num<<1|1]-=add[num];
????add[num]=0;
}
void build(int l,int r,int num){
????if(l==r){
????????a[num]=w[back[l]]-s[back[l]];
????????add[num]=s[back[l]];
????????return;
????}
????int mid=(l+r)>>1;
????build(l,mid,num<<1);build(mid+1,r,num<<1|1);
????update(num);
}
ll ask1(int l,int r,int num,int pos){
????if(l==r) return add[num];
????push(l,r,num);
????int mid=(l+r)>>1;
????if(mid>=pos) return ask1(l,mid,num<<1,pos);
????else return ask1(mid+1,r,num<<1|1,pos);
}
int get(int l,int r,int ql,int qr,int num,ll delta){
????push(l,r,num);
????if(l>=ql&&r<=qr){
????????if(a[num]>=delta) return l;
????????if(l==r) return 0;
????}
????int mid=(l+r)>>1;
????if(qr<=mid) return get(l,mid,ql,qr,num<<1,delta);
????if(ql>mid) return get(mid+1,r,ql,qr,num<<1|1,delta);
????int tmp=get(mid+1,r,ql,qr,num<<1|1,delta);
????if(!tmp||tmp>mid+1) return tmp;
????tmp=get(l,mid,ql,qr,num<<1,delta);
????if(!tmp) return mid+1;
????else return tmp;
}
void changew(int l,int r,int num,int pos){
????if(l==r){
????????a[num]=w[back[l]]-add[num];
????????return;
????}
????int mid=(l+r)>>1;
????push(l,r,num);
????if(mid>pos) changew(l,mid,num<<1,pos);
????else changew(mid+1,r,num<<1|1,pos);
????update(num);
}
void changes(int l,int r,int ql,int qr,int num,ll val){
????push(l,r,num);
????if(l>=ql&&r<=qr){
????????a[num]-=val;add[num]+=val;
????????return;
????}
????int mid=(l+r)>>1;
????if(mid>=ql) changes(l,mid,ql,qr,num<<1,val);
????if(mid<qr) changes(mid+1,r,ql,qr,num<<1|1,val);
????update(num);
}
int binary_search(int x,ll val){
????int pos,t;t=x;x=fa[x];
????while(fa[x]){
????????pos=get(1,n,p[top[x]],p[x],1,val);
????????if(!pos) break;
????????if(pos>p[top[x]]) return back[pos];
????????t=top[x];x=fa[t];
????}
????return t;
}
void work(int x,int y,ll val){
????while(top[x]!=top[y]) changes(1,n,p[top[x]],p[x],1,val),x=fa[top[x]];
????changes(1,n,p[y],p[x],1,val);??
}
ll getval(int x){
????return min(w[x],ask1(1,n,1,p[x]));
}
void change(int x,ll val){
????ll tmp=getval(x);
????w[x]+=val;
????changew(1,n,1,p[x]);
????ll delta=getval(x)-tmp;
????if(!delta) return;
????while(fa[x]){
????????int y=binary_search(x,delta);
????????if(x!=y) work(fa[x],y,delta);
????????x=fa[y];
????????if(!x) return;
????????tmp=getval(x);
????????work(x,x,delta);
????????delta=getval(x)-tmp;
????????if(!delta) return;
????}
}
int main(){
????memset(first,-1,sizeof(first));int i,t1;ll t2;char s[10];
????n=read();
????for(i=1;i<=n;i++) w[i]=read();
????for(i=1;i<n;i++){
????????t1=read();t2=read();
????????adde(t1,t2);
????}
????dfs(1,0);
????dfs2(1,1);
????build(1,n,1);
????int m=read();
????while(m--){
????????scanf("%s",s);
????????if(s[0]=='Q'){
????????????t1=read();
????????????printf("%lld\n",getval(t1));
????????}
????????else{
????????????t1=read();t2=read();
????????????change(t1,t2);
????????}
????}
}

原文地址:https://www.cnblogs.com/dedicatus545/p/9602550.html

时间: 2024-10-11 22:02:09

[bzoj4712] 洪水 [树链剖分+线段树+dp]的相关文章

Aizu 2450 Do use segment tree 树链剖分+线段树

Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show.php?pid=39566 Description Given a tree with n (1 ≤ n ≤ 200,000) nodes and a list of q (1 ≤ q ≤ 100,000) queries, process the queries in order and out

Hdu 3966 Aragorn&#39;s Story (树链剖分 + 线段树区间更新)

题目链接: Hdu 3966 Aragorn's Story 题目描述: 给出一个树,每个节点都有一个权值,有三种操作: 1:( I, i, j, x ) 从i到j的路径上经过的节点全部都加上x: 2:( D, i, j, x ) 从i到j的路径上经过的节点全部都减去x: 3:(Q, x) 查询节点x的权值为多少? 解题思路: 可以用树链剖分对节点进行hash,然后用线段树维护(修改,查询),数据范围比较大,要对线段树进行区间更新 1 #include <cstdio> 2 #include

【bzoj3589】动态树 树链剖分+线段树

题目描述 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0:这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1:小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝, 让你求出在这些树枝上的节点的果子数的和. 注意, 树枝之间可能会重合, 这时重合的部分的节点的果子只要算一次. 输入 第一行一个整数n(1<=n<=200,000), 即节点数. 接下来n-1行, 每行两个数字u,

BZOJ2243 (树链剖分+线段树)

Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. 解题分析 树链剖分+线段树. 开一个记录类型,记录某一段区间的信息.l 表示区间最左侧的颜色 , r 表示区间最右侧的颜色 , sum 表示区间中颜色段数量. 合并时判断一下左区间的右端点和有区间的左端点的颜色是否一样. 树上合并时需要用两个变量ans1,ans2来存储.ans1表示x往上走时形成的链的信息,

bzoj4304 (树链剖分+线段树)

Problem T2 (bzoj4304 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a . 操作 3 :询问某个节点 x 到根的路径中所有点的点权和. 解题分析 练手题.树链剖分+线段树. 参考程序 1 #include <cstdio> 2 #include <cstring> 3 #include <cmath> 4 #incl

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要

【bzoj4811】[Ynoi2017]由乃的OJ 树链剖分+线段树区间合并

题目描述 由乃正在做她的OJ.现在她在处理OJ上的用户排名问题.OJ上注册了n个用户,编号为1-",一开始他们按照编号 排名.由乃会按照心情对这些用户做以下四种操作,修改用户的排名和编号:然而由乃心情非常不好,因为Deus天 天问她题...因为Deus天天问由乃OI题,所以由乃去学习了一下OI,由于由乃智商挺高,所以OI学的特别熟练她 在RBOI2016中以第一名的成绩进入省队,参加了NOI2016获得了金牌保送 Deus:这个题怎么做呀? yuno:这个不是NOI2014的水题吗... Deu

HDU 2460 Network(双连通+树链剖分+线段树)

HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链剖分+线段树处理 代码: #include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; #pragma comment(linke

【bzoj1959】[Ahoi2005]LANE 航线规划 离线处理+树链剖分+线段树

题目描述 对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系. 星际空间站的Samuel II巨型计算机经过长期探测,已经锁定了Samuel星系中许多星球的空间坐标,并对这些星球从1开始编号1.2.3……. 一些先遣飞船已经出发,在星球之间开辟探险航线. 探险航线是双向的,例如从1号星球到3号星球开辟探险航线,那么从3号星球到1号星球也可以使用这条航线. 例如下图所示: 在5个星球之间,有5条探险航

HDU4897 (树链剖分+线段树)

Problem Little Devil I (HDU4897) 题目大意 给定一棵树,每条边的颜色为黑或白,起始时均为白. 支持3种操作: 操作1:将a->b的路径中的所有边的颜色翻转. 操作2:将所有 有且仅有一个点在a->b的路径中 的边的颜色翻转. 操作3:询问a->b的路径中的黑色边数量. 解题分析 考虑操作1,只需正常的树链剖分+线段树维护即可.用线段树维护每条边,tag_1[i]表示该区间中的黑色边数量. 考虑操作2,一个节点相邻的边只可能为重链和轻链,且重链的数目小于等于