bzoj4326 树链剖分 + 线段树 // 二分 lca + 树上差分
https://www.lydsy.com/JudgeOnline/problem.php?id=4326
题意:N个点的树上给M条树链,问去掉一条边的权值之后所有树链长度和的最大值最小是多少。
首先想到去掉的树边一定是最长链上的树边,所以产生的思路就是寻找出一条询问里的最长链之后依次枚举上面所有的边,询问去掉这条边之后其余所有边的最大值。
由于N和M都在30W,直接暴力肯定不行,考虑转换思维,变为维护不经过这条边上的所有链的最大值,在这个最大值和最长链 - 这条边权之中取较大的值就是去掉这条边之后整棵树最长的链。而维护去不经过这条边上的最大值可以考虑用线段树维护.所以整理得到如下的流程。
1.树剖维护出所有边形成的序列,维护一个前缀和,利用前缀和计算所有询问的链的长度并且找出最长链t
2.对于每个询问进行操作,计算出询问中的链在树剖序列上经过的区间,反向将没有经过的区间最大值更新为该询问树链的长度.
3.枚举最长链t经过的每一个树边,更新答案即可.
细节:
1.由于这是对边权而不是点权的维护.要注意将树边转化为除了1之外的点,方向为向叶子节点方向.在树剖序列里的初始权值为边权.
2.由于这波点转边的操作,在树剖跳结点的时候,最后top[u] == top[v]的时候,深度较小的那一个点是取不到的,显然1 - 2这条边中只包含了边2而没有边1,但是在x跳到fa[top[x]]的过程中则将整条链全部计算
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
inline int read(){int now=;register char c=getchar();for(;!isdigit(c);c=getchar());
for(;isdigit(c);now=now*+c-'',c=getchar());return now;}
#define For(i, x, y) for(int i=x;i<=y;i++)
#define _For(i, x, y) for(int i=x;i>=y;i--)
#define Mem(f, x) memset(f,x,sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x);
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);
#define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
#define LL long long
#define ULL unsigned long long
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
const double eps = 1e-;
const int maxn = 3e5 + ;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + ;
int N,M,K;
//链式前向星
struct Edge{
int to,dis,next;
}edge[maxn * ];
int head[maxn],tot;
void init(){
for(int i = ; i <= N ;i ++) head[i] = -;
tot = ;
}
void add(int u,int v,int w){
edge[tot].to = v;
edge[tot].next = head[u];
edge[tot].dis = w;
head[u] = tot++;
}
//树链剖分
int fa[maxn],dep[maxn],hson[maxn],size[maxn],ne[maxn];
int top[maxn],Index[maxn],To_num[maxn],nw[maxn];
void dfs1(int t,int la){
size[t] = ; int heavy = ;
for(int i = head[t]; ~i ; i = edge[i].next){
int v = edge[i].to;
if(v == la) continue;
dep[v] = dep[t] + ; fa[v] = t;
dfs1(v,t);
size[t] += size[v];
if(size[v] > heavy){
hson[t] = v;
ne[t] = edge[i].dis;
heavy = size[v];
}
}
}
int cnt;
void dfs2(int t,int la,int d){
nw[++cnt] = d; Index[t] = cnt;
top[t] = la; To_num[cnt] = t;
if(hson[t]) dfs2(hson[t],la,ne[t]);
for(int i = head[t]; ~i ; i = edge[i].next){
int v = edge[i].to;
if(v == hson[t] || v == fa[t]) continue;
dfs2(v,v,edge[i].dis);
}
}
//最大值线段树
struct Tree{
int l,r,MAX;
}tree[maxn << ];
void Build(int t,int l,int r){
tree[t].l = l; tree[t].r = r;
tree[t].MAX = ;
if(l == r) return;
int m = (l + r) >> ;
Build(t << ,l,m); Build(t << | ,m + ,r);
}
void Pushdown(int t){
tree[t << ].MAX = max(tree[t << ].MAX,tree[t].MAX);
tree[t << | ].MAX = max(tree[t << | ].MAX,tree[t].MAX);
}
void update(int t,int l,int r,int p){
if(p <= tree[t].MAX) return;
if(l <= tree[t].l && tree[t].r <= r){
tree[t].MAX = max(tree[t].MAX,p);
return;
}
Pushdown(t);
int m = (tree[t].l + tree[t].r) >> ;
if(r <= m) update(t << ,l,r,p);
else if(l > m) update(t << | ,l,r,p);
else{
update(t << ,l,m,p);
update(t << | ,m + ,r,p);
}
}
int query(int t,int p){
if(tree[t].l == tree[t].r) return tree[t].MAX;
Pushdown(t);
int m = (tree[t].l + tree[t].r) >> ;
if(p <= m) return query(t << ,p);
else return query(t << | ,p);
}
//前缀和
int pre[maxn];
void Build(){
for(int i = ; i <= N ; i ++){
pre[i] = pre[i - ] + nw[i];
}
}
struct Query{
int u,v,sum;
}Query[maxn];
int SUM(int u,int v){
int ans = ;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u,v);
ans += pre[Index[u]] - pre[Index[top[u]] - ];
u = fa[top[u]];
}
if(Index[u] > Index[v]) swap(u,v);
ans += pre[Index[v]] - pre[Index[u]];
return ans;
}
void update(int t){
int u = Query[t].u,v = Query[t].v;
int S = Query[t].sum;
vector<PII>Q;
Q.pb(mp(,));
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u,v);
int l = Index[top[u]],r = Index[u];
if(l <= r) Q.pb(mp(l,r));
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u,v);
if(u != ){
int l = Index[u] + ,r = Index[v];
if(l <= r) Q.pb(mp(l,r));
}
Q.pb(mp(N,N));
sort(Q.begin(),Q.end());
for(int i = ; i < Q.size() - ; i ++){
int l = Q[i].se + ,r = Q[i + ].fi - ;
if(l <= r){
update(,l,r,S);
}
}
}
void solve(int t){
int ans = Query[t].sum;
int u = Query[t].u,v = Query[t].v;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u,v);
for(int i = Index[top[u]]; i <= Index[u] ; i ++){
int mx = query(,i);
ans = min(ans,max(mx,Query[t].sum - nw[i]));
}
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u,v);
int l = Index[u] + ,r = Index[v];
for(int i = l; i <= r ; i ++){
int mx = query(,i);
ans = min(ans,max(mx,Query[t].sum - nw[i]));
}
Pri(ans);
}
int main(){
Sca2(N,M); init();
for(int i = ; i <= N - ; i ++){
int u = read(),v = read(),w = read();
add(u,v,w); add(v,u,w);
}
dfs1(,-); cnt = -; hson[] = ;dfs2(,,);
Build(); Build(,,N);
int t = ,MAX = ;
for(int i = ; i <= M ; i ++){
Query[i].u = read(); Query[i].v = read();
Query[i].sum = SUM(Query[i].u,Query[i].v);
update(i);
if(MAX < Query[i].sum){
MAX = Query[i].sum;
t = i;
}
}
solve(t);
return ;
}
树链剖分
方法2:
当然,对于一类最大值最小的问题,肯定会首先想到去二分,也就是t时间内可以通过的答案,所有比t大的一定都可以通过。
因此我们选择二分最终答案,然后检验是否成立。
考虑先预处理出所有询问的链长度,二分答案之后即可知道在这条链上需要删除的边至少是多长,最终将所有(至少删掉的边)取一个最大值就是最终可以删掉的最小长度,对于总共cnt条需要删除链的询问,我们只要判断树上的点是否足够长并且被所有cnt条边覆盖即可。
判断是否被cnt条边覆盖的操作考虑用树上差分。
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
inline int read(){int now=;register char c=getchar();for(;!isdigit(c);c=getchar());
for(;isdigit(c);now=now*+c-'',c=getchar());return now;}
#define For(i, x, y) for(int i=x;i<=y;i++)
#define _For(i, x, y) for(int i=x;i>=y;i--)
#define Mem(f, x) memset(f,x,sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x);
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);
#define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
#define LL long long
#define ULL unsigned long long
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
const double eps = 1e-;
const int maxn = 3e5 + ;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + ;
int N,M,K;
struct Edge{
int to,dis,next;
}edge[maxn * ];
int head[maxn],tot;
void init(){
for(int i = ; i <= N ; i ++) head[i] = -;
tot = ;
}
void add(int u,int v,int w){
edge[tot].to = v;
edge[tot].dis = w;
edge[tot].next = head[u];
head[u] = tot++;
}
//lca
int val[maxn];
const int SP = ;
int fa[maxn][],dep[maxn],dis[maxn];
void dfs(int t,int la){
dep[t] = dep[la] + ;
fa[t][] = la;
for(int i = ; i < SP; i ++) fa[t][i] = fa[fa[t][i - ]][i - ];
for(int i = head[t]; ~i ; i = edge[i].next){
int v = edge[i].to;
if(v == la) continue;
val[v] = edge[i].dis;
dis[v] = dis[t] + edge[i].dis;
dfs(v,t);
}
}
int lca(int u,int v){
if(dep[u] < dep[v]) swap(u,v);
int t = dep[u] - dep[v];
for(int i = SP - ; i >= ; i --) if(t & ( << i)) u = fa[u][i];
for(int i = SP - ; i >= ; i --){
int uu = fa[u][i],vv = fa[v][i];
if(uu != vv){
u = uu;v = vv;
}
}
return u == v?u:fa[u][];
}
//Road
struct Road{
int u,v,sum;
int l;
}road[maxn]; int num[maxn],cnt,MIN;
bool flag;
void dfs2(int t,int la){
if(flag) return;
for(int i = head[t]; ~i ; i = edge[i].next){
int v = edge[i].to;
if(v == la) continue;
dfs2(v,t);
num[t] += num[v];
}
if(num[t] == cnt && val[t] >= MIN) flag = ;
}
bool check(int t){
MIN = ; cnt = ;
for(int i = ; i <= N ; i ++ ) num[i] = ;
for(int i = ; i <= M ; i ++){
if(road[i].sum <= t) continue;
MIN = max(MIN,road[i].sum - t);
cnt++;
num[road[i].u]++; num[road[i].v]++;
num[road[i].l] -= ;
}
flag = ;
dfs2(,);
return flag;
}
int solve(){
int l = ,r = 3e8 + ;
int ans = ;
while(l <= r){
int m = (l + r) >> ;
if(check(m)){
ans = m;
r = m - ;
}else{
l = m + ;
}
}
return ans;
}
int main(){
N = read(); M = read(); init();
for(int i = ; i <= N - ; i ++){
int u = read(),v = read(),w = read();
add(u,v,w); add(v,u,w);
}
dis[] = ;dfs(,);
for(int i = ; i <= M ; i ++){
road[i].u = read(),road[i].v = read();
road[i].l = lca(road[i].u,road[i].v);
road[i].sum = dis[road[i].u] + dis[road[i].v] - * dis[road[i].l];
//cout << "lca" << road[i].l << "sum " << road[i].sum << endl;
}
Pri(solve());
return ;
}
bzoj4326 树链剖分 + 线段树 // 二分 lca + 树上差分的更多相关文章
- [BZOJ1146][CTSC2008]网络管理Network(二分+树链剖分+线段树套平衡树)
题意:树上单点修改,询问链上k大值. 思路: 1.DFS序+树状数组套主席树 首先按照套路,关于k大值的问题,肯定要上主席树,每个点维护一棵权值线段树记录它到根的信息. 关于询问,就是Que(u)+Q ...
- 【bzoj2402】陶陶的难题II 分数规划+树链剖分+线段树+STL-vector+凸包+二分
题目描述 输入 第一行包含一个正整数N,表示树中结点的个数.第二行包含N个正实数,第i个数表示xi (1<=xi<=10^5).第三行包含N个正实数,第i个数表示yi (1<=yi& ...
- 【bzoj3626】[LNOI2014]LCA 树链剖分+线段树
题目描述 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q次询问,每次询 ...
- BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)
前言 刚开始看着两道题感觉头皮发麻,后来看看题解,发现挺好理解,只是代码有点长. BZOJ 3672[NOI2014]购票 中文题面,题意略: BZOJ 3672[NOI2014]购票 设f(i)f( ...
- 【BZOJ-2325】道馆之战 树链剖分 + 线段树
2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 1153 Solved: 421[Submit][Statu ...
- 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 ...
- bzoj2243[SDOI2011]染色 树链剖分+线段树
2243: [SDOI2011]染色 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 9012 Solved: 3375[Submit][Status ...
- BZOJ3862Little Devil I——树链剖分+线段树
题目大意: 给一棵树,每条边可能是黑色或白色(起始都是白色),有三种操作: 1.将u到v路径上所有边颜色翻转(黑->白,白->黑) 2.将只有一个点在u到v路径上的边颜色翻转 3.查询u到 ...
- BZOJ2325[ZJOI2011]道馆之战——树链剖分+线段树
题目描述 口袋妖怪(又名神奇宝贝或宠物小精灵)红/蓝/绿宝石中的水系道馆需要经过三个冰地才能到达馆主的面前,冰地中 的每一个冰块都只能经过一次.当一个冰地上的所有冰块都被经过之后,到下一个冰地的楼梯才 ...
- BZOJ2819Nim——树链剖分+线段树+Nim游戏
题目描述 著名游戏设计师vfleaking,最近迷上了Nim.普通的Nim游戏为:两个人进行游戏,N堆石子,每回合可以取其中某一堆的任意多个,可以取完,但不可以不取.谁不能取谁输.这个游戏是有必胜策略 ...
随机推荐
- AHOI(十二省联考)2019 退役记
我也想退役失败.jpg Day 0 我才知道联考原来是4.5h? 下午居然还有讲题,感觉变得正规多了. 试机敲了LCT,NTT,SA,加起来花了大概40min,基本1A,感觉海星.键盘似乎有点过于灵敏 ...
- Linux服务器下安装vmware虚拟机
安装包 1.VMware 14 https://dl-sh-ctc-2.pchome.net/08/b7/VMware-Workstation-Full-14.1.3-9474260.x86_64.b ...
- D - Mayor's posters POJ - 2528 离散化+线段树 区间修改单点查询
题意 贴海报 最后可以看到多少海报 思路 :离散化大区间 其中[1,4] [5,6]不能离散化成[1,2] [2,3]因为这样破坏了他们的非相邻关系 每次离散化区间 [x,y]时 把y+1点也加入 ...
- P1140 相似基因 最长公共子序列
思路 类似于最长公共子序列 把一段基因和另外一段基因匹配 不够长的用空基因替换 #include<bits/stdc++.h> using namespace std; const in ...
- Pfsense2.34中文版
Pfsense2.34中文版 来源 https://forum.netgate.com/topic/112076/pfsense2-34%E4%B8%AD%E6%96%87%E7%89%88-%E8 ...
- Double.valueOf(0.0D) 分析
private Double price = Double.valueOf(0.0D); 查看Java API 文档如下: doubleValue public double doubleValue( ...
- [luogu1198][bzoj1012][JSOI2008]最大数【线段树+分块】
题目描述 区间查询最大值,结尾插入,强制在线. 分析 线段树可以做,但是练了一下分块,发现自己打错了两个地方,一个是分块的地方把/打成了%,还有是分块的时候标号要-1. 其他也没什么要多讲的. 代码 ...
- KVM改NAT为Bridge
kvm 改nat为桥接在安装一个拥有虚拟化功能的Linux操作系统(此处以CentOS为例),一般我们有两种方法:1.在光盘安装的时候安装好虚拟化包或者PXE服务器上配置好虚拟化包2.手动在没有安装虚 ...
- python装饰器中的计时器thd.strat用法
thd = KThread(target=_new_func, args=(), kwargs=new_kwargs) thd.start() thd.join(seconds) alive = th ...
- Electron一学习资源收集和练习demo
1.近日为了做项目查资料学习electron,简直头都要炸了,就官方的electron-quick-start的例子进行了基本的练习之后,不断的查资料终于发现一些有用的demo来看源代码学习,一遍看代 ...