【洛谷P2680】运输计划
题目链接
题目大意:
一棵\(n\)个点的带边权的数,给定\(m\)条树上两点间的路径,现在你可以让树上任意一条边的权值变为零,
问如何选边使得\(m\)条路径中边权和最大的路径的边权和最小
\(\mathcal{solution}\)
这是\(NOIP2015\)的\(Day2T3\),感觉难度是比较大的
我首先想到的是,要选的边一定在边权和最大的路径上
于是我们可以先用\(lca\)找出边权和最大的路径的起始点,复杂度\(O(mlogn)\)
然后一遍\(dfs\)找出这个路径上所有的边,复杂度\(O(n)\)
之后枚举这个路径上的边,将它置为零,再重新\(dfs\)更新\(f\)数组,
然后枚举每条路径,求每条路径的权值和
复杂度\(O(n(n+mlogn))\),只有\(40\)分
对于\(m=1\)的情况,我们可以直接一遍\(dfsO(n)\)解决
这样就有\(50\)分了
\(50\)分代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#define N 300010
using namespace std;
const int INF=0x3f3f3f3f;
inline int read(){
int x=0; char c=getchar();
while(c<'0')c=getchar();
while(c>='0') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x;
}
int n,m,Head[N],num=1;
struct NODE{
int to,w,next;
} e[N<<1];
inline void add(int x,int y,int w){
e[++num].to=y;
e[num].w=w;
e[num].next=Head[x];
Head[x]=num;
}
struct Data{ //路径的起始点
int x,y;
} a[N];
int f[N][25],sum[N][25],dep[N];
void dfs(int now,int fa){
f[now][0]=fa;
dep[now]=dep[fa]+1;
for(int i=1;(1<<i)<=dep[now];++i){
f[now][i]=f[f[now][i-1]][i-1];
sum[now][i]=sum[now][i-1]+sum[f[now][i-1]][i-1];
}
for(int i=Head[now];i;i=e[i].next)
if(e[i].to!=fa){
sum[e[i].to][0]=e[i].w;
dfs(e[i].to,now);
}
}
inline int Get_Sum(int x,int y){ //lca求边权和
int Sum=0;
if(dep[x]!=dep[y]){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0&&dep[x]>dep[y];--i)
if(dep[f[x][i]]>=dep[y])
Sum+=sum[x][i],x=f[x][i];
}
if(x==y) return Sum;
for(int i=20;i>=0;--i)
if(f[x][i]!=f[y][i]){
Sum+=sum[x][i]+sum[y][i];
x=f[x][i],y=f[y][i];
}
return Sum+sum[x][0]+sum[y][0];
}
int path[N],cnt;
bool dfs1(int now,int fa,int gl){ //已知起、始点,找出整条路径
if(now==gl) return 1;
for(int i=Head[now];i;i=e[i].next){
int v=e[i].to;
if(v==fa) continue;
path[++cnt]=i;
if(dfs1(v,now,gl)) return 1;
--cnt;
}
return 0;
}
int main()
{
n=read(),m=read();
int x,y,w;
for(int i=1;i<n;++i){
x=read(),y=read(),w=read();
add(x,y,w),add(y,x,w);
}
for(int i=1;i<=m;++i)
a[i].x=read(),a[i].y=read();
if(m==1){
dfs1(a[1].x,0,a[1].y);
int max2=0,sum2=0;
for(int i=1;i<=cnt;++i)
sum2+=e[path[i]].w,max2=max(max2,e[path[i]].w);
printf("%d\n",sum2-max2);
return 0;
}
dfs(1,0);
int k=0,maxx=0;
for(int i=1;i<=m;++i){
int t=Get_Sum(a[i].x,a[i].y);
if(t>maxx) maxx=t,k=i;
}
int minn=INF;
dfs1(a[k].x,0,a[k].y);
for(int i=1;i<=cnt;++i){
int k=path[i];
int temp=e[k].w;
e[k].w=e[k^1].w=0;
dfs(1,0);
e[k].w=e[k^1].w=temp;
int maxx=0;
for(int j=1;j<=m;++j)
maxx=max(maxx,Get_Sum(a[j].x,a[j].y));
minn=min(minn,maxx);
}
printf("%d\n",minn);
return 0;
}
我们考虑如何搞到\(100\)分
我们回到刚才的“题目大意”上来
问如何选边使得\(m\)条路径中\(\color{red}{边权和最大的路径的边权和最小}\)
这就提示我们要二分答案了
我们考虑二分一个答案\(mid\)表示最小的最大路径边权和
如何判断呢?
我们可以发现,最终被我们置为零的边 一定被 所有的一开始边权和大于\(mid\)的路径 覆盖了一遍
而当满足上面条件时,边权和最大的路径减去这条边的长度\(\leq mid\),那么这个\(mid\)就是可以满足的
我们可以用树上差分来求哪条边被所有一开始不满足条件的路径覆盖了,如果不存在这样的边,说明不存在方案满足当前\(mid\)
总的复杂度是\(O(mlogn+(m+n)logSum_{max})\)
然而最后一个点(\(luogu\)的\(\#13\))十分毒瘤,需要各种卡常(好歹没爆栈(雾
\(100\)分代码:
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define N 300010
#define root (20181111%n+1)
#define re
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
//#define swap(a,b) (a^=b^=a^=b)
inline int Max(int a, int b){
int diff=b-a;return b-(diff&(diff>>31));
}
const int INF=0x3f3f3f3f,ch_top=4e7+3;
char ch[ch_top],*now_r=ch-1;
inline int read(){
while(*++now_r<'0');
re int x=*now_r-'0';
while(*++now_r>='0') x=(x<<3)+(x<<1)+*now_r-'0';
return x;
}
int n,m,Head[N],num=1,mid,ha;
struct NODE{
int to,w,next;
} e[N<<1];
struct Data{
int x,y,sum,lca;
} a[N];
int f[N][25],sum[N][25],dep[N],Max_sum;
inline void dfs(int now,int fa){
f[now][0]=fa; dep[now]=dep[fa]+1;
for(re int i=1;(1<<i)<=dep[now];++i){
f[now][i]=f[f[now][i-1]][i-1];
sum[now][i]=sum[now][i-1]+sum[f[now][i-1]][i-1];
}
for(re int i=Head[now],v=e[i].to;i;i=e[i].next,v=e[i].to)
if(v!=fa)sum[v][0]=e[i].w,dfs(v,now);
}
inline void Get_Lca(int p){
int Sum=0,x=a[p].x,y=a[p].y;
if(dep[x]!=dep[y]){
if(dep[x]<dep[y]) x^=y^=x^=y;
for(re int i=19;dep[x]>dep[y];--i)
if(dep[f[x][i]]>=dep[y]){
Sum+=sum[x][i];
x=f[x][i];
}
}
if(x==y){a[p].sum=Sum;a[p].lca=x;return;}
for(re int i=19;i>=0;--i)
if(f[x][i]!=f[y][i]){
Sum+=sum[x][i]+sum[y][i];
x=f[x][i]; y=f[y][i];
}
a[p].sum=Sum+sum[x][0]+sum[y][0];
a[p].lca=f[x][0];
}
int diff[N];
bool dfs1(int now,int len,int fa){
int tot=diff[now];
for(re int i=Head[now],v=e[i].to;i;i=e[i].next,v=e[i].to)
if(v!=fa){
if(dfs1(v,e[i].w,now)) return 1;
tot+=diff[v];
}
diff[now]=tot;
if(tot==ha&&Max_sum-len<=mid)
return 1;
return 0;
}
inline bool check(){
ha=0;
memset(diff,0,sizeof(diff));
for(re int i=1;i<=m;++i)
if(a[i].sum>mid){
++ha;
++diff[a[i].x],++diff[a[i].y];
diff[a[i].lca]-=2;
}
return dfs1(root,0,0);
}
int main()
{
// freopen("a.in","r",stdin);
// int size = 256 << 20; //250M
// char*p=(char*)malloc(size) + size;
// __asm__("movl %0, %%esp\n" :: "r"(p) );
fread(ch,1,ch_top,stdin);
n=read(),m=read();
int x,y,w;
for(re int i=1;i<n;++i){
x=read(),y=read(),w=read();
e[++num].to=y;
e[num].w=w;
e[num].next=Head[x];
Head[x]=num;
e[++num].to=x;
e[num].w=w;
e[num].next=Head[y];
Head[y]=num;
}
dfs(root,0);
for(re int i=1;i<=m;++i){
a[i].x=read(),a[i].y=read();
Get_Lca(i);
Max_sum=Max(Max_sum,a[i].sum);
}
re int l(0),r=Max_sum;
while(l<r){
mid=(l+r)>>1;
if(check()) r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
【洛谷P2680】运输计划的更多相关文章
- 洛谷 P2680 运输计划-二分+树上差分(边权覆盖)
P2680 运输计划 题目背景 公元 20442044 年,人类进入了宇宙纪元. 题目描述 公元20442044 年,人类进入了宇宙纪元. L 国有 nn 个星球,还有 n-1n−1 条双向航道,每条 ...
- 洛谷 P2680 运输计划 解题报告
P2680 运输计划 题目背景 公元2044年,人类进入了宇宙纪元. 题目描述 公元2044年,人类进入了宇宙纪元. \(L\)国有\(n\)个星球,还有\(n-1\)条双向航道,每条航道建立在两个星 ...
- [NOIP2015] 提高组 洛谷P2680 运输计划
题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家 ...
- 洛谷P2680 运输计划 [LCA,树上差分,二分答案]
题目传送门 运输计划 Description 公元 2044 年,人类进入了宇宙纪元.L 国有 n 个星球,还有 n?1 条双向航道,每条航道建立在两个星球之间, 这 n?1 条航道连通了 L 国的所 ...
- 洛谷 P2680 运输计划(NOIP2015提高组)(BZOJ4326)
题目背景 公元 \(2044\) 年,人类进入了宇宙纪元. 题目描述 公元\(2044\) 年,人类进入了宇宙纪元. L 国有 \(n\) 个星球,还有 \(n-1\) 条双向航道,每条航道建立在两个 ...
- 洛谷 P2680 运输计划
题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家 ...
- 洛谷——P2680 运输计划
https://www.luogu.org/problem/show?pid=2680 题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每 ...
- 洛谷P2680 运输计划——树上差分
题目:https://www.luogu.org/problemnew/show/P2680 久违地1A了好高兴啊! 首先,要最大值最小,很容易想到二分: 判断当前的 mid 是否可行,需要看看有没有 ...
- 洛谷P2680 运输计划
大概就是二分+树上差分... 题意:给你树上m条路径,你要把一条边权变为0,使最长的路径最短. 最大的最小,看出二分(事实上我并没有看出来...) 然后二分k,对于所有大于k的边,树上差分求出最长公共 ...
- 洛谷P2680运输计划
传送门啦 要求的就是,把树上的一条边的权值设为0之后,所有路径中的最大值的最小值. 首先二分最大值,假设某次二分的最大值为x,我们首先找出所有大于x的路径(也就是我们需要通过改权缩短的路径),并把路径 ...
随机推荐
- 学习Golang的步骤建议
一.快速入门 通过快速入门可以宏观的了解Go相关知识.快速入门可以去学习 go-tour 国内可以访问的中文版的 go-tour 地址有下面一些: http://gotour.qizhanming.c ...
- CentOS-Linux系统下安装Tomcat
步骤1:解压Tomcat 命令: unzip apache-tomcat-8.5.20.zip 步骤2:将tomcat 移动到“/usr/local/src/java/tomcat8.5”下并重命名 ...
- 思维导图_Python_内置函数
- 普通平衡树Tyvj1728、luogu P3369 (treap)
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入x数 删除x数(若有多个相同的数,因只删除一个) 查询x数的排名(若有多个相同的数,因输出最小的排名) 查询排名为x的 ...
- ztree 获取当前选中节点的子节点集合
功能:获取当前选中节点的子节点id集合. 步骤:1.获取当前节点 2.用ztree的方法transformToArray()获取当前选中节点(含选中节点)的子节点对象集合. 3.遍历集合,取出需要的值 ...
- ORA-16014: log 3 sequence# 540 not archived, no available destinations
https://blog.csdn.net/zonelan/article/details/7329369
- swiper移动端日历-1
先上图: 说明:这是基于移动端的,对于PC端浏览器支持不是很好(我测的结果是IE无效),另外这个swiper是4.x版本的 思路: 先引用css <link href="css/ ...
- Ubuntu 安装 PhpMyAdmin 图文教程
Ubuntu 安装 PhpMyAdmin 管理 MySQL 数据库 PhpMyAdmin 是一个用 PHP 编写的软件工具,可以通过 web方式控制和操作 MySQL 数据库.通过 phpMyAdmi ...
- GPU 编程语言 Harlan
Harlan 是一个声明式的.GPU 领域特定的编程语言.目前主要是用于技术实现和优化的测试用途.该语言很小,用于简化浏览新的分析器和优化. 支持的操作系统: Mac OS X 10.6 (Snow ...
- 基于Vue的WebApp项目开发(四)
实现新闻咨询页面 目录结构 步骤一:创建newslist.vue文件 <template> <div id="tml"> <!--使用mui框架,实现 ...