您的位置:首页 > 其它

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

2016-08-25 19:45 274 查看
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
4000
);
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 2100000000

using 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
,*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
,x,m,A
,B
;
ll fa[20]
,bz[20]
,bza[20]
,bzb[20]
;

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
dfb5
-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);
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;
}


大概就是这个样子,如果有什么问题,或错误,请在评论区提出,谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息