BZOJ 1912(树的直径+LCA)
题面
分析
显然,如果不加边,每条边都要走2次,总答案为2(n-1)
考虑k=1的朴素情况:
加一条边(a,b),这条边和树上a->b的路径形成一个环,这个环上的边只需要走一遍,所以答案会减少dist(a,b)-1 (a->b的路径少走一边,但是又多加了一条边,最终答案为2*(n-1)-dist(a,b)+1
显然dist(a,b)应该最大,求树上的直径\(L_1\)即可
接着推广到k=2的情况
加第二条边时,又会形成一个环,这个环和第一条边形成的环可能有重叠,重叠部分要走两遍,答案又会增加
因此,我们把直径上的边权取反(1变为-1),在直径取反后的树上再次求直径,设直径为\(L_2\)
答案就是\(2(n-1)-(L_1-1)-(L_2-1)\)
注意两次BFS求直径的方法在负权图上求直径不成立,需要用树形DP
具体方法如下:
1.在原图上用两次BFS求出直径\(L_1\)端点a,b
2.通过LCA和树上差分将直径上的边标记,并取反边权
3.用树形DP求出新直径\(L_2\)
代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#define maxlog 32
#define maxn 400005
#define INF 0x7fffffff
using namespace std;
int n,k;
struct edge {
int from;
int to;
int next;
int len;
} E[maxn*2];
int sz=1;
int head[maxn];
void add_edge(int u,int v,int w) {
sz++;
E[sz].from=u;
E[sz].to=v;
E[sz].len=w;
E[sz].next=head[u];
head[u]=sz;
}
namespace k_1 {
struct node {
int x;
int t;
node() {
}
node(int u,int dis) {
x=u;
t=dis;
}
};
int vis[maxn];
node bfs(int s) {
node now,ans;
queue<node>q;
q.push(node(s,0));
memset(vis,0,sizeof(vis));
ans.t=-INF;
while(!q.empty()) {
now=q.front();
q.pop();
int x=now.x;
vis[x]=1;
if(now.t>ans.t) {
ans.t=now.t;
ans.x=now.x;
}
for(int i=head[x]; i; i=E[i].next) {
int y=E[i].to;
if(!vis[y]) {
q.push(node(y,now.t+1));
}
}
}
return ans;
}
void solve() {
node p=bfs(1);
node q=bfs(p.x);
printf("%d\n",2*(n-1)-q.t+1);
}
}
namespace k_2 {
int log2n;
struct node {
int x;
int t;
node() {
}
node(int u,int dis) {
x=u;
t=dis;
}
};
int vis[maxn];
int mark[maxn];
node bfs(int s,int tim) {
node now,ans;
queue<node>q;
q.push(node(s,0));
memset(vis,0,sizeof(vis));
ans.t=-INF;
ans.x=s;
if(n==1) return node(1,0);
while(!q.empty()) {
now=q.front();
q.pop();
int x=now.x;
vis[x]=1;
if(tim==1) {
if(now.t>=ans.t&&now.x!=s) {
ans.t=now.t;
ans.x=now.x;
}
}else{
if(now.t>=ans.t) {
ans.t=now.t;
ans.x=now.x;
}
}
for(int i=head[x]; i; i=E[i].next) {
int y=E[i].to;
if(!vis[y]) {
q.push(node(y,now.t+E[i].len));
}
}
}
return ans;
}
int anc[maxn][maxlog];
int deep[maxn];
void dfs1(int x,int fa) {
deep[x]=deep[fa]+1;
anc[x][0]=fa;
for(int i=1; i<=log2n; i++) {
anc[x][i]=anc[anc[x][i-1]][i-1];
}
for(int i=head[x]; i; i=E[i].next) {
int y=E[i].to;
if(y!=fa) {
dfs1(y,x);
}
}
}
void dfs2(int x,int fa) {
for(int i=head[x]; i; i=E[i].next) {
int y=E[i].to;
if(y!=fa) {
dfs2(y,x);
mark[x]+=mark[y];
}
}
}
int lca(int x,int y) {
if(deep[x]<deep[y]) swap(x,y);
for(int i=log2n; i>=0; i--) {
if(deep[anc[x][i]]>=deep[y]) x=anc[x][i];
}
if(x==y) return x;
for(int i=log2n; i>=0; i--) {
if(anc[x][i]!=anc[y][i]) {
x=anc[x][i];
y=anc[y][i];
}
}
return anc[x][0];
}
int len=0;
int d[maxn];
void dp(int x,int fa){
d[x]=0;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa){
dp(y,x);
len=max(len,d[x]+d[y]+E[i].len);
d[x]=max(d[x],d[y]+E[i].len);
}
}
}
void solve() {
int u,v;
int ans=2*(n-1);
log2n=log2(n)+1;
dfs1(1,0);
node p=bfs(1,1);
node q=bfs(p.x,2);
ans=ans-q.t+1;
memset(mark,0,sizeof(mark));
mark[p.x]++;
mark[q.x]++;
mark[lca(p.x,q.x)]-=2;
dfs2(1,0);
memset(head,0,sizeof(head));
memset(E,0,sizeof(E));
sz=1;
for(int i=2; i<=n; i++) {
add_edge(i,anc[i][0],mark[i]==1?-1:1);
add_edge(anc[i][0],i,mark[i]==1?-1:1);
}
memset(d,-0x3f,sizeof(d));
dp(1,0);
ans=ans-len+1;
printf("%d\n",ans);
}
}
int main() {
int u,v;
scanf("%d %d",&n,&k);
sz=1;
for(int i=1; i<n; i++) {
scanf("%d %d",&u,&v);
add_edge(u,v,1);
add_edge(v,u,1);
}
if(k==1) k_1::solve();
else k_2::solve();
}
BZOJ 1912(树的直径+LCA)的更多相关文章
- 【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树的直径/LCA/树的点分治
题目描述 农夫约翰有N(2≤N≤40000)个农场,标号1到N,M(2≤M≤40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样, 图中农场用F ...
- BZOJ 2282 & 树的直径
SDOI2011的Dayx第2题 题意: 在树中找到一条权值和不超过S的链(为什么是链呢,因为题目中提到“使得路径的两端都是城市”,如果不是链那不就不止两端了吗——怎么这么机智的感觉...),使得不在 ...
- BZOJ 1912: [Apio2010]patrol 巡逻 (树的直径)(详解)
题目: https://www.lydsy.com/JudgeOnline/problem.php?id=1912 题解: 首先,显然当不加边的时候,遍历一棵树每条边都要经过两次.那么现在考虑k==1 ...
- bzoj 1912 巡逻(树直径)
Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Ou ...
- 【BZOJ】1912: [Apio2010]patrol 巡逻(树的直径)
题目 传送门:QWQ 分析 $ k=1 $ 时显然就是树的直径 $ k=2 $ 时怎么做呢? 做法是把一开始树的直径上的边的边权改成$ -1 $,那么当我们第二次用这些边做环时就抵消了一开始的贡献. ...
- 51nod 1766 树上的最远点对 | LCA ST表 线段树 树的直径
51nod 1766 树上的最远点对 | LCA ST表 线段树 树的直径 题面 n个点被n-1条边连接成了一颗树,给出a~b和c~d两个区间,表示点的标号请你求出两个区间内各选一点之间的最大距离,即 ...
- bzoj 1776: [Usaco2010 Hol]cowpol 奶牛政坛——树的直径
农夫约翰的奶牛住在N (2 <= N <= 200,000)片不同的草地上,标号为1到N.恰好有N-1条单位长度的双向道路,用各种各样的方法连接这些草地.而且从每片草地出发都可以抵达其他所 ...
- XTU1267:Highway(LCA+树的直径)
传送门 题意 有n个小镇,Bobo想要建造n-1条边,并且如果在u到v建边,那么花费是u到v的最短路长度(原图),问你最大的花费. 分析 比赛的时候没做出来,QAQ 我们首先要找到树的直径起点和终点, ...
- [51nod 1766]树上的最远点对 (树的直径+ST表求lca+线段树)
[51nod 1766]树上的最远点对 (树的直径+ST表求lca+线段树) 题面 给出一棵N个点的树,Q次询问一点编号在区间[l1,r1]内,另一点编号在区间[l2,r2]内的所有点对距离最大值.\ ...
随机推荐
- JavaScript中Number(),parseInt()和parseFloat()区别
parseInt() 函数可解析一个字符串,并返回一个整数; parseFloat() 函数可解析一个字符串,并返回一个浮点数, 以上都是截取转换,具体代码如下: alert(parseInt(&qu ...
- 【知识强化】第七章 输入/输出系统 7.3 I/O接口
下面我们进入第七章的第三节,I/O接口. I/O接口呢就是解决了外设和主机之间的一个连接的问题.那么我们这一节就要来看一下I/O接口它有哪些功能,以及它是怎么组成的,还有就是我们主机如何来定位到那样一 ...
- 一、模型验证CoreWebApi 管道方式(非过滤器处理)
一.新建.Net Core的MVC项目添加WebApi控制器的方式 using System; using System.Collections.Generic; using System.Linq; ...
- Linux性能优化从入门到实战:12 内存篇:Swap 基础
内存资源紧张时,可能导致的结果 (1)OOM 杀死大内存CPU利用率又低的进程(系统内存耗尽的情况下才生效:OOM 触发的时机是基于虚拟内存,即进程在申请内存时,如果申请的虚拟内存加上服务器实际已用的 ...
- Angular 一个简单的指令实现 阻止事件扩散
//指令定义 @Directive({ selector: `click-stop-propagation` events: 'stopClick($event)' }) class ClickSto ...
- python3-filter
Python内建的filter()函数用于过滤序列. 和map()类似,filter()也接收一个函数和一个序列.和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是 ...
- mysql数据精度丢失问题深入探讨
不要盲目的说float和double精度可能发生丢失,而是说在存取时因为精度不一致会发生丢失,当然这里的丢失指的是扩展或者截断了,丢失了原有的精度.decimal是好,但不是说不会发生任何精度丢失.如 ...
- Task5.NB_SVM_LDA
参考:https://blog.csdn.net/u013710265/article/details/72780520 贝叶斯公式就一行: P(Y|X)=P(X|Y)P(Y)P(X) 而它其实是由以 ...
- 一探究竟:善用 MaxCompute Studio 分析 SQL 作业
头疼的问题 MaxCompute 用户一个常见的问题是:同一个周期任务,为什么最近几天比之前慢了很多?或者为什么之前都能按时产出的作业最近经常破线? 通常来说,引起作业执行变慢的原因有:quota 组 ...
- 基于mpvue搭建小程序项目框架
简介: mpvue框架对于从没有接触过小程序又要尝试小程序开发的人员来说,无疑是目前最好的选择.mpvue从底层支持 Vue.js 语法和构建工具体系,同时再结合相关UI组件库,便可以高效的实现小程序 ...