BZOJ1975[Sdoi2010]魔法猪学院——可持久化可并堆+最短路树
题目描述
输入
输出
样例输入
1 2 1.5
2 1 1.5
1 3 3
2 3 1.5
3 4 1.5
1 4 1.5
样例输出
提示
样例解释
有意义的转换方式共4种:
1->4,消耗能量 1.5
1->2->1->4,消耗能量 4.5
1->3->4,消耗能量 4.5
1->2->3->4,消耗能量 4.5
显然最多只能完成其中的3种转换方式(选第一种方式,后三种方式仍选两个),即最多可以转换3份样本。
如果将 E=14.9 改为 E=15,则可以完成以上全部方式,答案变为 4。
数据规模
占总分不小于 10% 的数据满足 N <= 6,M<=15。
占总分不小于 20% 的数据满足 N <= 100,M<=300,E<=100且E和所有的ei均为整数(可以直接作为整型数字读入)。
所有数据满足 2 <= N <= 5000,1 <= M <= 200000,1<=E<=107,1<=ei<=E,E和所有的ei为实数。
首先对反向边跑最短路建出以$n$为根的最短路树。
对于一条从$1$到$n$的路径上的边集$S$(边有顺序),除去在最短路上的边,剩下的边组成的集合为$S'$(按$S$中的顺序),那么对于$S'$中顺序相邻的两条边$(u,v)$和$(s,t)$,$s$一定是$v$的祖先或相同点(因为$s$与$v$在树上直接相连或由树边相连)。
我们设$val_{e}=d_{v}+w-d_{u}$,其中$e$为一条不在最短路树上的边,$d_{i}$表示点$i$到$n$的最短路长度,$w$为这条边的边权,$u,v$分别为这条边的起点和终点。
那么一条从$1$到$n$的路径长度$len$就可以表示成$len=d_{1}+\sum\limits_{e\in S'}^{ }val_{e}$。
那么问题就转化成求第$k$小的$S'$。
最小的$S'$显然是空集,即$S$为从$1$到$n$的最短路。
那么现在考虑如何获得一个新的相对较小的边集,对于一个边集$S'$有两种方法(假设$S'$中最后一条边为$(u,v)$):
1、将$(u,v)$换成以$u$为起点没被选取过的$val$最小的一条非树边。
2、在$S'$的最后添加一条新的非树边满足这条非树边的起点是$v$在最短路树上的祖先,当然也是使这条边的$val$尽量小。
那么我们只需要维护一个小根堆,每次取出堆顶的边集并将通过这个边集获得的新的边集加入堆中即可。
对于两种获得新边集的方法,第一种只需要对于每个点维护一个小根堆即可,但第二种需要维护出这个点到$n$路径上所有点的堆中的信息。
我们将每个堆变为可并堆并可持久化,即每个点的堆保存了从这个点到根路径上所有点的堆的信息。
将每个点的可并堆像线段树合并的可持久化一样与子节点堆合并即可。
$bzoj$与$luogu$的精度不同,代码附上两个版本的。
$bzoj$
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<vector> #include<bitset> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long #define pr pair<double,int> const double eps = 1e-6; using namespace std; int tot; int cnt; int head[5010]; int to[400010]; int next[400010]; double val[400010]; int root[5010]; int ls[2000010]; int rs[2000010]; int end[2000010]; double v[2000010]; double d[5010]; int dis[2000010]; int n,m; double E,z; int x,y; int vis[5010]; int from[5010]; int f[400010]; vector<int>e[5010]; int ans; priority_queue< pr,vector<pr>,greater<pr> >q; void add(int x,int y,double z) { next[++tot]=head[x]; head[x]=tot; to[tot]=y; val[tot]=z; } int build(double val,int to) { int rt=++cnt; v[rt]=val; end[rt]=to; dis[rt]=1; return rt; } int merge(int x,int y) { if(!x||!y) { return x+y; } if(v[x]-v[y]>=eps) { swap(x,y); } int rt=++cnt; ls[rt]=ls[x],rs[rt]=rs[x],end[rt]=end[x],dis[rt]=dis[x],v[rt]=v[x]; rs[rt]=merge(rs[rt],y); if(dis[ls[rt]]<dis[rs[rt]]) { swap(ls[rt],rs[rt]); } dis[rt]=dis[rs[rt]]+1; return rt; } void dfs(int x) { int size=e[x].size(); for(int i=0;i<size;i++) { int to=e[x][i]; root[to]=merge(root[to],root[x]); dfs(to); } } int main() { scanf("%d%d%lf",&n,&m,&E); for(int i=1;i<=m;i++) { scanf("%d%d%lf",&x,&y,&z); add(x,y,z); add(y,x,z); } memset(d,127,sizeof(d)); d[n]=0; q.push(make_pair(d[n],n)); while(!q.empty()) { int now=q.top().second; q.pop(); if(vis[now]) { continue; } vis[now]=1; for(int i=head[now];i;i=next[i]) { if(i&1) { continue; } if(d[to[i]]>d[now]+val[i]) { from[to[i]]=i-1; d[to[i]]=d[now]+val[i]; q.push(make_pair(d[to[i]],to[i])); } } } for(int i=1;i<n;i++) { f[from[i]]=1; e[to[from[i]]].push_back(i); } for(int i=1;i<=tot;i+=2) { if(!f[i]) { root[to[i+1]]=merge(root[to[i+1]],build(val[i]+d[to[i]]-d[to[i+1]],to[i])); } } dfs(n); if(E-d[1]>=eps) { E-=d[1]; ans++; } if(root[1]) { q.push(make_pair(v[root[1]],root[1])); } while(!q.empty()) { int now=q.top().second; double res=q.top().first; if(E-d[1]-res<eps) { break; } q.pop(); E-=d[1]+res; ans++; if(ls[now]) { q.push(make_pair(v[ls[now]]+res-v[now],ls[now])); } if(rs[now]) { q.push(make_pair(v[rs[now]]+res-v[now],rs[now])); } if(root[end[now]]) { q.push(make_pair(res+v[root[end[now]]],root[end[now]])); } } printf("%d",ans); }
$luogu$
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<vector> #include<bitset> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long #define pr pair<long double,int> const long double eps = 1e-8; using namespace std; int tot; int cnt; int head[5010]; int to[400010]; int next[400010]; long double val[400010]; int root[5010]; int ls[4000010]; int rs[4000010]; int end[4000010]; long double v[4000010]; long double d[5010]; int dis[4000010]; int n,m; long double E,z; int x,y; int vis[5010]; int from[5010]; int f[400010]; vector<int>e[5010]; int ans; priority_queue< pr,vector<pr>,greater<pr> >q; void add(int x,int y,long double z) { next[++tot]=head[x]; head[x]=tot; to[tot]=y; val[tot]=z; } int build(long double val,int to) { int rt=++cnt; v[rt]=val; end[rt]=to; dis[rt]=1; return rt; } int merge(int x,int y) { if(!x||!y) { return x+y; } if(v[x]-v[y]>=eps) { swap(x,y); } int rt=++cnt; ls[rt]=ls[x],rs[rt]=rs[x],end[rt]=end[x],dis[rt]=dis[x],v[rt]=v[x]; rs[rt]=merge(rs[rt],y); if(dis[ls[rt]]<dis[rs[rt]]) { swap(ls[rt],rs[rt]); } dis[rt]=dis[rs[rt]]+1; return rt; } void dfs(int x) { int size=e[x].size(); for(int i=0;i<size;i++) { int to=e[x][i]; root[to]=merge(root[to],root[x]); dfs(to); } } int main() { scanf("%d%d%Lf",&n,&m,&E); for(int i=1;i<=m;i++) { scanf("%d%d%Lf",&x,&y,&z); add(x,y,z); add(y,x,z); } for(int i=1;i<=n;i++) { d[i]=(long double)50000000000001.0; } d[n]=0; q.push(make_pair(d[n],n)); while(!q.empty()) { int now=q.top().second; q.pop(); if(vis[now]) { continue; } vis[now]=1; for(int i=head[now];i;i=next[i]) { if(i&1) { continue; } if(d[to[i]]+eps>d[now]+val[i]) { from[to[i]]=i-1; d[to[i]]=d[now]+val[i]; q.push(make_pair(d[to[i]],to[i])); } } } for(int i=1;i<n;i++) { f[from[i]]=1; e[to[from[i]]].push_back(i); } for(int i=1;i<=tot;i+=2) { if(!f[i]) { root[to[i+1]]=merge(root[to[i+1]],build(val[i]+d[to[i]]-d[to[i+1]],to[i])); } } dfs(n); if(E-d[1]>=eps) { E-=d[1]; ans++; } if(root[1]) { q.push(make_pair(v[root[1]],root[1])); } while(!q.empty()) { int now=q.top().second; long double res=q.top().first; if(E-d[1]-res<-eps) { break; } q.pop(); E-=d[1]+res; ans++; if(ls[now]) { q.push(make_pair(v[ls[now]]+res-v[now],ls[now])); } if(rs[now]) { q.push(make_pair(v[rs[now]]+res-v[now],rs[now])); } if(root[end[now]]) { q.push(make_pair(res+v[root[end[now]]],root[end[now]])); } } printf("%d",ans); }
- 浅析Hadoop大数据分析与应用
- WCF技术剖析之三十三:你是否了解WCF事务框架体系内部的工作机制?[上篇]
- Java豆瓣电影爬虫——小爬虫成长记(附源码)
- Java豆瓣电影爬虫——抓取电影详情和电影短评数据
- 日本科技振兴理事:AI科学家应有红线意识
- Java豆瓣电影爬虫——使用Word2Vec分析电影短评数据
- 实践重于理论——创建一个监控程序探测WCF的并发处理机制
- 分布式科学计算与Docker
- 学习SpringMVC——说说视图解析器
- Java豆瓣电影爬虫——模拟登录的前世今生与验证码的爱恨情仇
- Java豆瓣电影爬虫——减少与数据库交互实现批量插入
- 谈谈C# 4.0新特性“缺省参数”的实现
- 如何实现对上下文(Context)数据的统一管理 [提供源代码下载]
- 不再和人工智能对弈?柯洁:我要食言了
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 弄它!!! 深入了解STP生成树协议
- kali linux下的常用bash命令
- shell脚本快速入门之-----linux设置 自定义脚本开机启动,一键式部署网卡配置文件
- jdbc连接oracle语法
- java实现数据库连接的工具类
- shell脚本快速入门之-----正则三剑客之一grep用法大全!!!
- 【网页特效】11 个文本输入和 6 个按钮操作 特效库
- shell脚本快速入门之-----正则三剑客之二sed用法大全!!!
- JSP中的Cookie
- 傅里叶变换
- shell脚本快速入门之-----shell脚本练习100例!!!
- java监听器
- shell脚本快速入门之-----函数
- shell脚本快速入门之-----循环(for、while、until)
- ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析