【noip】开车旅行 平衡树 倍增 treap tree

2024-06-04 22:32

本文主要是介绍【noip】开车旅行 平衡树 倍增 treap tree,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

noip2012年day1最后一题
感觉2012年的都好难写 疫情控制也是。。

描述

小A和小B决定利用假期外出旅行,他们将想去的城市从1到N编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市i 的海拔高度为Hi,城市i 和城市j 之间的距离d[i,j]恰好是这两个城市海拔高度之差的绝对值,即d[i,j] = |Hi - Hj|。
旅行过程中,小A和小B轮流开车,第一天小A开车,之后每天轮换一次。他们计划选择一个城市S作为起点,一直向东行驶,并且最多行驶X公里就结束旅行。小A和小B的驾驶风格不同,小B总是沿着前进方向选择一个最近的城市作为目的地,而小A总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出X公里,他们就会结束旅行。
在启程之前,小A想知道两个问题:
1.对于一个给定的X=X0,从哪一个城市出发,小A开车行驶的路程总数与小B行驶的路程总数的比值最小(如果小B的行驶路程为0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小A开车行驶的路程总数与小B行驶的路程总数的比值都最小,则输出海拔最高的那个城市。
2. 对任意给定的X=Xi 和出发城市Si,小A开车行驶的路程总数以及小B行驶的路程总数。

格式

输入格式
第一行包含一个整数N,表示城市的数目。
第二行有N个整数,每两个整数之间用一个空格隔开,依次表示城市1到城市N的海拔高度,即H1,H2,……,Hn,且每个Hi 都是不同的。
第三行包含一个整数X0。
第四行为一个整数M,表示给定M组Si和Xi。
接下来的M行,每行包含2个整数Si 和Xi,表示从城市Si 出发,最多行驶Xi 公里。
输出格式
输出共M+1行。
第一行包含一个整数S0,表示对于给定的X0,从编号为S0的城市出发,小A开车行驶
的路程总数与小B行驶的路程总数的比值最小。
接下来的M行,每行包含2个整数,之间用一个空格隔开,依次表示在给定的Si 和Xi 下小A行驶的里程总数和小B行驶的里程总数。

样例

样例输入1

4
2 3 1 4
3
4
1 3
2 3
3 3
4 3

样例输出1

1
1 1
2 0
0 0
0 0

样例输入2

10
4 5 6 1 2 3 7 8 9 10
7
10
1 7
2 7
3 7
4 7
5 7
6 7
7 7
8 7
9 7
10 7

样例输出2

2
3 2
2 4
2 1
2 4
5 1
5 1
2 1
2 0
0 0
0 0

限制

每个测试点1s

提示

对于30%的数据,有1≤N≤20,1≤M≤20;
对于40%的数据,有1≤N≤100,1≤M≤100;
对于50%的数据,有1≤N≤100,1≤M≤1,000;
对于70%的数据,有1≤N≤1,000,1≤M≤10,000;
对于100%的数据,有1≤N≤100,000,1≤M≤10,000,-1,000,000,000≤Hi≤1,000,000,000,0≤X0≤1,000,000,000,1≤Si≤N,0≤Xi≤1,000,000,000,数据保证Hi 互不相同。

来源

Noip2012提高组复赛Day1T3

刚拿到这道题题目又长,又乱,不好读懂,仔细看一下,我们可以讲问题分为以下3个大的步骤。
1、找到在每个城市小A和小B回前往哪个城市
2、利用上面得到的信息枚举每个起点,解决第一个问
3、利用上面得到的信息直接模拟,解决第二个问

毫无疑问,直接这样写100%超时,时间复杂度 O(n2) ,我们一步一步的来优化

一、平衡树快速查找第一大和次大
这里我写的是treap tree,写起来最简单,用起来也很爽。
查找的话我这里要说一下,我本来想一口气直接查找出来两个值,但再找出最接近的值后如果还要找下一个就必须遍历本来不需要遍历的树,那样查找的效率大大下降,还不如两次查找
然后我实在不知道怎么写,乱搞,还建立结构体,用sort(因为他说海拔小的排在前面这个不是很好处理)

int get_second_min(tree2 *tree,int x)
{int a=0,b=0,c=0,d=0,n1=0,n2=0,n3=0,n4=0;//a,b,c,d分别表示向上查找的最小,次小,向下查找的最大,次大,n1~n4分别表示对应的位置find_min_upper(tree,x,a,n1);if(n1!=0)find_min_upper(tree,a,b,n2);find_min_lower(tree,x,c,n3);if(n3!=0)find_min_lower(tree,c,d,n4);a=n1==0?INF:abs(a-x);b=n2==0?INF:abs(b-x);c=n3==0?INF:abs(c-x);d=n4==0?INF:abs(d-x);//没找到是0,必须处理一下,方便排序node arr[4]={{a,1,n1},{b,1,n2},{c,0,n3},{d,0,n4}};//结构体暴力排序,com见下面代码sort(arr,arr+4,com);return arr[1].c;
}

二、快速查找走过路径
这个就直接用倍增,就是我一来想得太麻烦,考虑了每个点A出发与B出发的情况,实际上完全不用考虑这个的,因为如果要倍增的话都是走的偶数次,不会改变下次谁第一个开车,你说最后一个是1?但它不影响其他的了,直接单独存一下就好了
然后还有就是这里要多建立2个倍增数组,分别拿来存A和B走过的距离
bz:

//我这里专门把走两步拿出来算,因为走两步的一半是A,一半是B,后面无论前面一半还是后面一半都是A先开车
for(int i=1;i<=n;i++){if(i+1>n)break;fa[1][i]=B[A[i]];bz[1][i]=bz[0][i]+abs(h[B[A[i]]]-h[A[i]]);bza[1][i]=bz[0][i];bzb[1][i]=abs(h[B[A[i]]]-h[A[i]]);}for(int j=2;j<=17;j++)for(int i=1;i<=n;i++){if(i+(1<<(j-1))>n)break;fa[j][i]=fa[j-1][fa[j-1][i]];bz[j][i]=bz[j-1][i]+bz[j-1][fa[j-1][i]];bza[j][i]=bza[j-1][i]+bza[j-1][fa[j-1][i]];[fa[j-1][1][i]];bzb[j][i]=bzb[j-1][i]+bzb[j-1][fa[j-1][i]];}

基本上就是这两个优化处理,这样优化以后时间复杂度就优化到了 O(logn) 虽然常数有点大,我还是卡到0.8s过了。

然后最后就是代码,写得比较长,前面是平衡树,中间是倍增,后面是问1和问2,然后这个调用的东西比较多,很多变量名和我平时常用的重复了,中间各种双写,大写,比较懒,见谅了。(然后我现在就又要来写注释)
不多说了,代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<cmath>
#define N 100005
#define M 20005
#define ll long long
#define INF 2100000000using namespace std;struct tree2
{tree2 *son[2];int n,v,no;int com(int x){if(x==n)return -1;return x>n?1:0;}
}dizhi[N],*null,*root=null;
int t;
//treap tree主题数据结构 
void rotate(tree2 *&tree,int d)//d=0左旋 d=1右旋
{tree2 *k=tree->son[d^1];tree->son[d^1]=k->son[d];k->son[d]=tree;tree=k;
}void insert(tree2 *&tree,int x,int i)//treap tree 插入 
{if(tree==null){tree=&dizhi[t++];tree->v=rand();tree->n=x;tree->no=i;tree->son[0]=tree->son[1]=null;return ;}int d=tree->com(x);insert(tree->son[d],x,i);if(tree->son[d]->v>tree->v)rotate(tree,d^1);
}void  find_min_upper(tree2 *tree,int x,int &first,int &no1)
//在tree里面找一个比x大的第一个数 (比x大的最小值)no1为返回标号 
{if(tree==null)return ;if(tree->n<=x)//当前节点的数比x小,直接找右子树 {find_min_upper(tree->son[1],x,first,no1);return ;}if(tree->n<first||no1==0)//更新 {first=tree->n;no1=tree->no;}find_min_upper(tree->son[0],x,first,no1);//这里如果找到了只用更新左子树 if(no1==0)find_min_upper(tree->son[1],x,first,no1);
}void  find_min_lower(tree2 *tree,int x,int &first,int &no1)
//在tree里面找一个比x小的第一个数 (比x小的最大值)no1为返回标号 
{if(tree==null)return ;if(tree->n>=x)//当前节点的数比x大,直接找左子树 {find_min_lower(tree->son[0],x,first,no1);return ;}if(tree->n>first||no1==0)//更新 {first=tree->n;no1=tree->no;}find_min_lower(tree->son[1],x,first,no1);//这里如果找到了只用更新右子树 if(no1==0)find_min_lower(tree->son[0],x,first,no1);
}int get_min(tree2 *tree,int x)//找一个离x最近的值 
{int a=0,c=0,n1=0,n3=0;//这里要找2个,一个比x大的,一个比x小的,同理,下面找次近的就要找4个 find_min_upper(tree,x,a,n1);find_min_lower(tree,x,c,n3);if(n3==0)return n1;if(n1==0)return n3;c=abs(c-x);a=abs(a-x);return c<=a?n3:n1;
}struct node
{int a,b,c;
};bool com(const node &a,const node &b)
{return a.a<b.a||(a.a==b.a&&a.b<b.b);
}int get_second_min(tree2 *tree,int x)
{int a=0,b=0,c=0,d=0,n1=0,n2=0,n3=0,n4=0;//a,b,c,d分别表示向上查找的最小,次小,向下查找的最大,次大,n1~n4分别表示对应的位置find_min_upper(tree,x,a,n1);if(n1!=0)find_min_upper(tree,a,b,n2);find_min_lower(tree,x,c,n3);if(n3!=0)find_min_lower(tree,c,d,n4);a=n1==0?INF:abs(a-x);b=n2==0?INF:abs(b-x);c=n3==0?INF:abs(c-x);d=n4==0?INF:abs(d-x);//没找到是0,必须处理一下,方便排序node arr[4]={{a,1,n1},{b,1,n2},{c,0,n3},{d,0,n4}};sort(arr,arr+4,com);return arr[1].c;
}int n,h[N],x,m,A[N],B[N];
ll fa[20][N],bz[20][N],bza[20][N],bzb[20][N];void get_bz()
{//我这里专门把走两步拿出来算,因为走两步的一半是A,一半是B,后面无论前面一半还是后面一半都是A先开车for(int i=1;i<=n;i++){if(i+1>n)break;fa[1][i]=B[A[i]];//  fa[1][1][i]=fa[0][0][fa[0][1][i]];bz[1][i]=bz[0][i]+abs(h[B[A[i]]]-h[A[i]]);//  bz[1][1][i]=bz[0][1][i]+bz[0][0][fa[0][1][i]];bza[1][i]=bz[0][i];//  bza[1][1][i]=bz[0][0][i+1];bzb[1][i]=abs(h[B[A[i]]]-h[A[i]]);//  bzb[1][1][i]=bz[0][1][i];}for(int j=2;j<=17;j++)for(int i=1;i<=n;i++){if(i+(1<<(j-1))>n)break;fa[j][i]=fa[j-1][fa[j-1][i]];//  fa[j][1][i]=fa[j-1][1][fa[j-1][1][i]];bz[j][i]=bz[j-1][i]+bz[j-1][fa[j-1][i]];//  bz[j][1][i]=bz[j-1][1][i]+bz[j-1][1][fa[j-1][1][i]];bza[j][i]=bza[j-1][i]+bza[j-1][fa[j-1][i]];//  bza[j][1][i]=bza[j-1][1][i]+bza[j-1][1][fa[j-1][1][i]];bzb[j][i]=bzb[j-1][i]+bzb[j-1][fa[j-1][i]];//  bzb[j][1][i]=bzb[j-1][1][i]+bzb[j-1][1][fa[j-1][1][i]];}
}const double eps=1e-8;
//当我写到这里的时候我是崩溃的,为什么还要用这个。。。 
bool equal(double a,double b)
{if(a<=b+eps&&a>=b-eps)return 1;return 0;
}double check(int s)//第一个问,检测从s节点出发,返回比值 
{int pos=s,xx=x,aa=0,bb=0,bo=1;for(int i=17;i>=0;i--)if(fa[i][pos]!=0&&bz[i][pos]<=xx){bo=0;xx-=bz[i][pos];aa+=bza[i][pos];bb+=bzb[i][pos];pos=fa[i][pos];}if(bb==0)return 1e100;//无限大,不多说 return (double)aa/(double)bb;
}void work(int s,int xx,int &aa,int &bb)//第二个问,实际上和第一个问没什么区别 
{int pos=s;aa=0,bb=0;for(int i=17;i>=0;i--)if(fa[i][pos]!=0&&bz[i][pos]<=xx){xx-=bz[i][pos];aa+=bza[i][pos];bb+=bzb[i][pos];pos=fa[i][pos];}
}int main()
{freopen("drive.in","r",stdin);freopen("drive.out","w",stdout);cin>>n;for(int i=1;i<=n;i++)scanf("%d",&h[i]);insert(root,h[n],n);for(int i=n-1;i>=1;i--)//这里从后向前变插入变找,保证找到的节点在i的右边 {if(i==n-1){A[i]=0;B[i]=get_min(root,h[i]);insert(root,h[i],i);continue;}A[i]=get_second_min(root,h[i]);B[i]=get_min(root,h[i]);insert(root,h[i],i);}cin>>x;for(int i=1;i<=n-1;i++){fa[0][i]=A[i];bz[0][i]=abs(h[A[i]]-h[i]);bza[0][i]=bz[0][i];//fa[0][1][i]=B[i];//bz[0][1][i]=abs(h[B[i]]-h[i]);}get_bz();//这里是倍增,后面没有什么好说的了 double mi=check(1);int X=1;for(int i=2;i<=n;i++){double t=check(i);if(t<mi||((equal(t,mi))&&h[i]>h[X])){mi=t;X=i;}}cout<<X<<'\n';cin>>m;int aa,bb;for(int i=1;i<=m;i++){int s,xx;scanf("%d%d",&s,&xx);work(s,xx,aa,bb);printf("%d %d\n",aa,bb);}return 0;
}

大概就是这个样子,如果有什么问题,或错误,请在评论区提出,谢谢。

这篇关于【noip】开车旅行 平衡树 倍增 treap tree的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1031305

相关文章

树(Tree)——《啊哈!算法》

树 什么是树?树是一种特殊的图,不包含回路的连通无向树。 正因为树有着“不包含回路”这个特点,所以树就被赋予了很多特性。 一棵树中的任意两个结点有且仅有唯一的一条路径连通。一棵树如果有n个结点,那么它一定恰好有n-1条边。在一棵树中加一条边将会构成一个回路。 一棵树有且只有一个根结点。 没有父结点的结点称为根结点(祖先)。没有子结点的结点称为叶结点。如果一个结点既不是根结点也不是叶

226 Invert Binary Tree

//226 Invert Binary Tree//算法思路:主要使用递归算法public class Solution {public TreeNode invertTree(TreeNode root) {//1 出口 空节点if (root==null)return null;//2 递归 调用自己TreeNode left = root.left;TreeNode right = ro

FHQ Treap模版(luogu P3369)

FHQ Treap模版(自用),带注释 #include<bits/stdc++.h>using namespace std;const int N=1e5+10;int n,root,idx;struct node{int l,r;int val,key,size;}tr[N];int getnew(int v){tr[++idx].val=v;//权值tr[idx].key=rand(

写给大数据开发:你真的“慢“了吗?揭秘技术与职场的平衡艺术

你是否曾经在深夜里,面对着一个棘手的数据处理问题,感到无比沮丧?或者在一次重要的项目汇报中,突然语塞,无法清晰地表达你的技术方案?作为一名大数据开发者,这些场景可能再熟悉不过。但别担心,因为你并不孤单。让我们一起探讨如何在这个瞬息万变的行业中,既磨练技术利刃,又培养职场软实力。 目录 技术与时间的赛跑1. 长远视角的重要性2. 复利效应在技能学习中的应用 跨界思维:数据结构教我们的职场智

如何限制与管控员工上网行为?四个方法让员工效率倍增!【企业员工上网行为管理】

在信息化时代,员工的上网行为直接影响着工作效率和企业的安全性。不当的网络使用,如浏览与工作无关的网站、下载不安全的文件,可能导致工作效率低下,甚至引发安全风险。因此,许多企业正在积极寻找有效的措施来管控员工的上网行为,以确保工作效率的提升。 以下是四个常见且有效的员工上网行为管理方法,帮助企业实现更高效的网络管理。 方法一:配置网络防火墙进行访问限制 最基础的员工上网行为管理方法是通过配置防

代码随想录 -- 二叉树 -- 平衡二叉树

110. 平衡二叉树 - 力扣(LeetCode) 思路:仍然是递归调用 1. 定义一个递归函数 count 用来计算二叉树的层数 2. isBalanced 函数:如果传入根节点为空返回真;如果根节点 | 左子树的层数 - 右子树的层数 | 大于1,返回假;最后返回根节点左子树、右子树是否是平衡二叉树。 class Solution(object):def count(self,root

社交平台找旅游搭子一起旅行靠谱吗?答案是不要太爽!

哈喽小伙伴们,今天要跟大家分享一个超级棒的小程序——咕哇找搭子!作为一个热爱自由行的人,最头疼的就是找不到志同道合的小伙伴。但自从用了这个咕哇小程序后,一切都变得简单又充满乐趣啦!🎉 上个月,我计划去云南旅行,就试着在咕哇上发布了我的行程信息。没想到很快就收到了几位朋友的回应,其中一位叫小莲的朋友特别投缘。我们不仅目的地一样,就连兴趣爱好都出奇地相似,于是我们就决定一起出发啦!👭

Sorry!Hbase的LSM Tree就是可以为所欲为!

我们先抛出一个问题: LSM树是HBase里使用的非常有创意的一种数据结构。在有代表性的关系型数据库如MySQL、SQL Server、Oracle中,数据存储与索引的基本结构就是我们耳熟能详的B树和B+树。而在一些主流的NoSQL数据库如HBase、Cassandra、LevelDB、RocksDB中,则是使用日志结构合并树(Log-structured Merge Tree,LSM Tr

旅行商问题 | Matlab基于混合粒子群算法GA-PSO的旅行商问题TSP

目录 效果一览基本介绍建模步骤程序设计参考资料 效果一览 基本介绍 混合粒子群算法GA-PSO是一种结合了遗传算法(Genetic Algorithm, GA)和粒子群优化算法(Particle Swarm Optimization, PSO)的优化算法。在解决旅行商问题(Traveling Salesman Problem, TSP)时,这种混合算法可以结合两种算法的优点

【spring】does not have member field ‘com.sun.tools.javac.tree.JCTree qualid

spring-in-action-6-samples 的JDK版本 最小是11,我使用 了22: jdk21 jdk22 都与lombok 不兼容,必须使用兼容版本, 否则报错: thingsboard 的大神解释了: java: java.lang.NoSuchFieldError: Class com