题目链接

(BZOJ) https://www.lydsy.com/JudgeOnline/problem.php?id=4732

(UOJ) http://uoj.ac/problem/268

题解

首先考虑,给定一条路径,如何计算与其相交的所有路径的权值和?显然一条路径和另一条路径相交,当且仅当这条路径的LCA在另一条路径上,或者另一条路径的LCA在这条路径上。那么我们考虑维护两个数组\(a\)和\(b\), 分别表示以某点为LCA的路径权值和以及覆盖这个点但不以该点为LCA的路径权值和,则路径\((u,v)\)的价值为\(\sum_{p \in (u,v)}b_p+a_{LCA(u,v)}\).

下面我们要求路径的最大权值,考虑链分治。在一条重链上统计LCA在这条重链上的链的答案。假设一条链和该重链相交的部分为\((u,v)\)其中\(u\)是\(v\)的祖先也是链的LCA。若\(u\ne v\), 该链的权值为\(h_u+h_v+\sum^v_{i=u}a_i+b_u\),否则为\(h_u+h'_u+a_u+b_u\), 其中\(h_u\)和\(h'_u\)分别为\(u\)点的轻儿子引出的链的\(\sum a_i\)的最大值和次大值。一条重链的权值等于上面的式子在\(u\)和\(v\)变化时的最大值,考虑维护这个最大值,对于\(u\ne v\)的情况,需要采取最大子段和的维护方式;对于\(u=v\)的情况,就是简单的区间修改区间最大值,都可以用线段树维护。总答案等于所有重链的权值最大值,因此我们开一个multiset维护所有重链的权值。

考虑修改。修改时我们需要把一条链上的\(b\)增加一个权值\(w\)(可能是负数),再把LCA处的\(a\)增加一个权值\(w\). 修改\(b\)在重链上是区间修改,这个可以简单地进行打标记,把后缀和和最大子段和加上某一个值。修改\(a\)在重链上是单点修改。但是一个\(a\)的修改还会影响到它所在重链顶端的父亲的\(h\), 那个点的\(h\)改变又会影响它所在重链顶端的父亲的\(h\)……直到根,因此需要不断跳重链修改。这条链的\(\sum a_i\)最大值就相当于我们在线段树中维护的“最大前缀和”那个量(\(\sum^v_{i=u}a_i+h_v\)), 因此可以直接重新查出,设为\(sa_i\). 为了快速维护\(sa\)的最大次大,我们只需对每个点再开一个multiset记录一下即可。每次对一条链进行修改后,需要重新计算这条链的权值,更新总答案multiset.

时间复杂度\(O(n\log^2n)\).

注意multiset用两个堆实现会快,线段树给每条链开一棵比\([1,n]\)开一棵要快。

代码

人丑常数大……

#include<bits/stdc++.h>
#define llong long long
using namespace std; inline int read()
{
int w=1,s=0;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch)) {s=s*10+ch-'0';ch=getchar();}
return w*s;
} const int N = 1e5;
struct Edge
{
int nxt,v;
} e[(N<<1)+3];
struct Query
{
int u,v,w;
} qr[N+3];
int fe[N+3];
int fa[N+3];
int dfn[N+3],idfn[N+3];
int dep[N+3];
int sz[N+3];
int hvs[N+3];
int tpn[N+3],btn[N+3];
llong a[N+3],h[N+3],h2[N+3],hv[N+3],sa[N+3];
int n,q,en,dfnn; struct Multiset
{
priority_queue<llong> q1,q2;
void insert(llong x) {q1.push(x);}
void erase(llong x) {q2.push(x);}
llong getmx()
{
while(!q2.empty()&&q1.top()==q2.top()) {q1.pop(),q2.pop();}
return q1.top();
}
llong getmx2()
{
llong mx = getmx(); erase(mx); llong ret = getmx(); insert(mx); return ret;
}
};
Multiset lt[N+3]; int ltn[N+3];
Multiset s; void addedge(int u,int v)
{
en++; e[en].v = v;
e[en].nxt = fe[u]; fe[u] = en;
} void dfs1(int u)
{
sz[u] = 1; hvs[u] = 0;
for(int i=fe[u]; i; i=e[i].nxt)
{
int v = e[i].v;
if(v==fa[u]) continue;
fa[v] = u;
dep[v] = dep[u]+1;
dfs1(v);
sz[u] += sz[v];
if(hvs[u]==0||sz[v]>sz[hvs[u]]) {hvs[u] = v;}
}
}
void dfs2(int u)
{
dfn[u] = ++dfnn; idfn[dfnn] = u;
if(!hvs[u]) {btn[u] = u; s.insert(0ll); return;}
tpn[hvs[u]] = tpn[u]; dfs2(hvs[u]); btn[u] = btn[hvs[u]];
for(int i=fe[u]; i; i=e[i].nxt)
{
int v = e[i].v;
if(v==fa[u]||v==hvs[u]) continue;
lt[u].insert(0); ltn[u]++;
tpn[v] = v;
dfs2(v);
}
} struct Data
{
llong v,lv,rv,lrv;
Data() {v = lv = rv = lrv = 0ll;}
Data(llong _v,llong _lv,llong _rv,llong _lrv):v(_v),lv(_lv),rv(_rv),lrv(_lrv) {}
};
Data operator +(const Data &x,const Data &y)
{
Data ret;
ret.lrv = x.lrv+y.lrv;
ret.lv = max(x.lv,x.lrv+y.lv);
ret.rv = max(y.rv,x.rv+y.lrv);
ret.v = max(max(x.v,y.v),x.rv+y.lv);
return ret;
}
struct SegmentTree
{
struct SgTNode
{
Data x; llong tag;
} sgt[(N<<2)+3];
void maketag(int u,llong tag)
{
sgt[u].x.rv += tag; sgt[u].x.v += tag;
sgt[u].tag += tag;
}
void pushdown(int u)
{
llong tag = sgt[u].tag;
if(tag)
{
maketag(u<<1,tag); maketag(u<<1|1,tag);
sgt[u].tag = 0ll;
}
}
void upd(int u,int le,int ri,int pos)
{
if(le==ri) {int pos0 = idfn[pos]; sgt[u].x = Data(a[pos0]+sgt[u].tag+h[pos0],a[pos0]+h[pos0],a[pos0]+sgt[u].tag+h[pos0],a[pos0]); return;}
pushdown(u);
int mid = (le+ri)>>1;
if(pos<=mid) upd(u<<1,le,mid,pos);
else upd(u<<1|1,mid+1,ri,pos);
sgt[u].x = sgt[u<<1].x+sgt[u<<1|1].x;
}
void addb(int u,int le,int ri,int lb,int rb,llong x)
{
if(le>=lb && ri<=rb) {maketag(u,x); return;}
pushdown(u);
int mid = (le+ri)>>1;
if(lb<=mid) addb(u<<1,le,mid,lb,rb,x);
if(rb>mid) addb(u<<1|1,mid+1,ri,lb,rb,x);
sgt[u].x = sgt[u<<1].x+sgt[u<<1|1].x;
}
Data query(int u,int le,int ri,int lb,int rb)
{
if(le>=lb && ri<=rb) {return sgt[u].x;}
pushdown(u);
int mid = (le+ri)>>1; Data ret;
if(rb<=mid) ret = query(u<<1,le,mid,lb,rb);
else if(lb>mid) ret = query(u<<1|1,mid+1,ri,lb,rb);
else ret = query(u<<1,le,mid,lb,rb)+query(u<<1|1,mid+1,ri,lb,rb);
sgt[u].x = sgt[u<<1].x+sgt[u<<1|1].x;
return ret;
}
} sgt;
struct SegmentTree2
{
struct SgTNode
{
llong tag,mx;
} sgt[(N<<2)+3];
void maketag(int u,llong tag)
{
sgt[u].mx += tag; sgt[u].tag += tag;
}
void pushdown(int u)
{
llong tag = sgt[u].tag;
if(tag)
{
maketag(u<<1,tag); maketag(u<<1|1,tag);
sgt[u].tag = 0;
}
}
void add(int u,int le,int ri,int lb,int rb,llong x)
{
if(le>=lb && ri<=rb) {maketag(u,x); return;}
pushdown(u);
int mid = (le+ri)>>1;
if(lb<=mid) add(u<<1,le,mid,lb,rb,x);
if(rb>mid) add(u<<1|1,mid+1,ri,lb,rb,x);
sgt[u].mx = max(sgt[u<<1].mx,sgt[u<<1|1].mx);
}
llong query(int u,int le,int ri,int lb,int rb)
{
if(le>=lb && ri<=rb) {return sgt[u].mx;}
pushdown(u);
int mid = (le+ri)>>1; llong ret = 0ll;
if(lb<=mid) ret = max(ret,query(u<<1,le,mid,lb,rb));
if(rb>mid) ret = max(ret,query(u<<1|1,mid+1,ri,lb,rb));
sgt[u].mx = max(sgt[u<<1].mx,sgt[u<<1|1].mx);
return ret;
}
} sgt2; void calcpath(int u)
{
int x = tpn[u],y = btn[u];
s.erase(hv[x]);
Data tmp = sgt.query(1,1,n,dfn[x],dfn[y]); hv[x] = tmp.v;
llong tmp2 = sgt2.query(1,1,n,dfn[x],dfn[y]); hv[x] = max(hv[x],tmp2);
s.insert(hv[x]);
}
void addvpath(int u,int v,llong w)
{
sgt.addb(1,1,n,dfn[u],dfn[v],w);
sgt2.add(1,1,n,dfn[u],dfn[v],w);
calcpath(u);
}
void addpath(int u0,int v0,llong w)
{
int u = u0,v = v0;
while(tpn[u]!=tpn[v])
{
if(dep[tpn[u]]>dep[tpn[v]]) {addvpath(tpn[u],u,w); u = fa[tpn[u]];}
else {addvpath(tpn[v],v,w); v = fa[tpn[v]];}
}
if(dep[u]>dep[v]) swap(u,v);
if(u!=v) {addvpath(idfn[dfn[u]+1],v,w);}
a[u] += w; sgt.upd(1,1,n,dfn[u]); sgt2.add(1,1,n,dfn[u],dfn[u],w); calcpath(u);
int x = fa[tpn[u]];
while(x)
{
lt[x].erase(sa[tpn[u]]);
Data tmp = sgt.query(1,1,n,dfn[tpn[u]],dfn[btn[u]]); sa[tpn[u]] = tmp.lv;
lt[x].insert(sa[tpn[u]]);
llong tmp1 = h[x]; h[x] = lt[x].getmx(); sgt.upd(1,1,n,dfn[x]); sgt2.add(1,1,n,dfn[x],dfn[x],h[x]-tmp1);
if(ltn[x]>=2)
{
llong tmp2 = h2[x]; h2[x] = lt[x].getmx2();
sgt2.add(1,1,n,dfn[x],dfn[x],h2[x]-tmp2);
}
calcpath(x);
u = x; x = fa[tpn[x]];
}
} int main()
{
scanf("%d%d",&n,&q);
for(int i=1; i<n; i++)
{
int u,v; scanf("%d%d",&u,&v);
addedge(u,v); addedge(v,u);
}
dep[1] = 1; dfs1(1);
tpn[1] = 1; dfs2(1);
for(int i=1; i<=q; i++)
{
char opt[5]; scanf("%s",opt); int u,v,w;
if(opt[0]=='+') {scanf("%d%d%d",&qr[i].u,&qr[i].v,&qr[i].w); u = qr[i].u,v = qr[i].v,w = qr[i].w;}
else {int id; scanf("%d",&id); u = qr[id].u,v = qr[id].v,w = -qr[id].w;}
addpath(u,v,w);
printf("%lld\n",s.getmx());
}
return 0;
}

BZOJ 4732 UOJ #268 [清华集训2016]数据交互 (树链剖分、线段树)的更多相关文章

  1. BZOJ.1036 [ZJOI2008]树的统计Count ( 点权树链剖分 线段树维护和与最值)

    BZOJ.1036 [ZJOI2008]树的统计Count (树链剖分 线段树维护和与最值) 题意分析 (题目图片来自于 这里) 第一道树链剖分的题目,谈一下自己的理解. 树链剖分能解决的问题是,题目 ...

  2. BZOJ.4034 [HAOI2015]树上操作 ( 点权树链剖分 线段树 )

    BZOJ.4034 [HAOI2015]树上操作 ( 点权树链剖分 线段树 ) 题意分析 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 ...

  3. BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)

    前言 刚开始看着两道题感觉头皮发麻,后来看看题解,发现挺好理解,只是代码有点长. BZOJ 3672[NOI2014]购票 中文题面,题意略: BZOJ 3672[NOI2014]购票 设f(i)f( ...

  4. BZOJ4732. [清华集训2016]数据交互(树链剖分+线段树+multiset)

    题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=4732 题解 首先,一个比较显然的结论是:对于一棵有根树上的两条链 \((x_1, y_1 ...

  5. BZOJ 1146: [CTSC2008]网络管理Network 树链剖分+线段树+平衡树

    1146: [CTSC2008]网络管理Network Time Limit: 50 Sec  Memory Limit: 162 MBSubmit: 870  Solved: 299[Submit] ...

  6. BZOJ 3672: [Noi2014]购票( 树链剖分 + 线段树 + 凸包 )

    s弄成前缀和(到根), dp(i) = min(dp(j) + (s(i)-s(j))*p(i)+q(i)). 链的情况大家都会做...就是用栈维护个下凸包, 插入时暴力弹栈, 查询时就在凸包上二分/ ...

  7. bzoj 3626 : [LNOI2014]LCA (树链剖分+线段树)

    Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q ...

  8. bzoj 4034: [HAOI2015]树上操作 树链剖分+线段树

    4034: [HAOI2015]树上操作 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 4352  Solved: 1387[Submit][Stat ...

  9. bzoj 1036: [ZJOI2008]树的统计Count 树链剖分+线段树

    1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 16294  Solved: 6645[Submit ...

随机推荐

  1. 在论坛中出现的比较难的sql问题:3(row_number函数 分组查询)

    原文:在论坛中出现的比较难的sql问题:3(row_number函数 分组查询) 最近,在论坛中,遇到了不少比较难的sql问题,虽然自己都能解决,但发现过几天后,就记不起来了,也忘记解决的方法了. 所 ...

  2. 数据仓库之抽取数据:openrowset函数带bulk操作符的用法

    原文:数据仓库之抽取数据:openrowset函数带bulk操作符的用法 在做数据仓库时,最重要的就是ETL的开发,而在ETL开发中的第一步,就是要从原OLTP系统中抽取数据到过渡区中,再对这个过渡区 ...

  3. Eclipse 反编译工具 jad

    ** 1 下载 jad工具 ** 2 将.exe文件放在jdk安装路径下,里面有java ,javac 等命令,然后将jad.jar放在eclipse的dropins目录下 ** 3 启动eclips ...

  4. g++ 生成C++ .so库文件,并调用示例

    Tags: g++ C++ so library   在Linux系统下用g++命令编译C++程序.也可以生成so,a链接库   示例一 编译时链接so库 Test.h 文件内容   Main.cpp ...

  5. Zookeeper 安装及集群配置注意点

    Zookeeper在ubuntu下安装及集群搭建,关于集群搭建,网上很多文章 可以参考:https://www.ibm.com/developerworks/cn/opensource/os-cn-z ...

  6. Java中常见时间类的使用

    模拟场景针对于常用的操作API,比如流操作(字符流.字节流),时间操作等,仅仅了解概念性的定义终究是无法了解该类的用途和使用方式:这种情况在使用的时候便一脸茫然,脑海中映射不到对应的知识点.本篇博客将 ...

  7. PHP常见算法

    算法的概念:解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作.一个问题可以有多种算法,每种算法都不同的效率.一个算法具有的特征:有穷,确切,输入,输出,可行 ...

  8. Oracle 11G 数据库迁移【expdp/impdp】

    转自:http://www.th7.cn/db/Oracle/201802/263773.shtml 0x01 环境 A 机器,操作系统 CentOS7.3,Oracle版本:11G,IP地址:192 ...

  9. sql创建临时表并且插入数据

    if OBJECT_ID('tempdb..#temp') is not null drop table #temp select * into #temp from ( --select * fro ...

  10. C++ 语句函数再探

    1. 表达式只计算,抛弃计算结果: 2. 空语句什么也不做: 3.switch case语句漏写break,将会从匹配到的情况开始执行,直到语句结束 int main() { ; i + ; //表达 ...