题解报告——倍增之noip2012开车旅行

题目网址:

https://www.luogu.org/problemnew/show/P1081

题目描述

小 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

说明

【输入输出样例 1 说明】

各个城市的海拔高度以及两个城市间的距离如上图所示。

如果从城市 1 出发,可以到达的城市为 2,3,4,这几个城市与城市 1 的距离分别为 1,1,2,但是由于城市 3 的海拔高度低于城市 2,所以我们认为城市 3 离城市 1 最近,城市 2 离城市1 第二近,所以小 A 会走到城市 2。到达城市 2 后,前面可以到达的城市为 3,4,这两个城市与城市 2 的距离分别为 2,1,所以城市 4 离城市 2 最近,因此小 B 会走到城市 4。到达城市 4 后,前面已没有可到达的城市,所以旅行结束。

如果从城市 2 出发,可以到达的城市为 3,4,这两个城市与城市 2 的距离分别为 2,1,由于城市 3 离城市 2 第二近,所以小 A 会走到城市 3。到达城市 3 后,前面尚未旅行的城市为4,所以城市 4 离城市 3 最近,但是如果要到达城市 4,则总路程为 2+3=5>3,所以小 B 会直接在城市 3 结束旅行。

如果从城市 3 出发,可以到达的城市为 4,由于没有离城市 3 第二近的城市,因此旅行

还未开始就结束了。

如果从城市 4 出发,没有可以到达的城市,因此旅行还未开始就结束了。

【输入输出样例 2 说明】

当 X=7 时, 如果从城市 1 出发,则路线为 1 -> 2 -> 3 -> 8 -> 9,小 A 走的距离为 1+2=3,小 B 走的距离为 1+1=2。(在城市 1 时,距离小 A 最近的城市是 2 和 6,但是城市 2 的海拔更高,视为与城市 1 第二近的城市,所以小 A 最终选择城市 2;走到 9 后,小 A 只有城市 10 可以走,没有第 2 选择可以选,所以没法做出选择,结束旅行)

如果从城市 2 出发,则路线为 2 -> 6 -> 7 ,小 A 和小 B 走的距离分别为 2,4。

如果从城市 3 出发,则路线为 3 -> 8 -> 9,小 A 和小 B 走的距离分别为 2,1。

如果从城市 4 出发,则路线为 4 -> 6 -> 7,小 A 和小 B 走的距离分别为 2,4。

如果从城市 5 出发,则路线为 5 -> 7 -> 8 ,小 A 和小 B 走的距离分别为 5,1。

如果从城市 6 出发,则路线为 6 -> 8 -> 9,小 A 和小 B 走的距离分别为 5,1。

如果从城市 7 出发,则路线为 7 -> 9 -> 10,小 A 和小 B 走的距离分别为 2,1。

如果从城市 8 出发,则路线为 8 -> 10,小 A 和小 B 走的距离分别为 2,0。

如果从城市 9 出发,则路线为 9,小 A 和小 B 走的距离分别为 0,0(旅行一开始就结

束了)。

如果从城市10出发,则路线为 10,小A 和小B 走的距离分别为0,0。

从城市 2 或者城市 4 出发小 A 行驶的路程总数与小 B 行驶的路程总数的比值都最小,但是城市 2 的海拔更高,所以输出第一行为 2。

对于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≤100,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 互不相同。

一点小心得

分析:这道题不会做,看别人题解看了一整天,算是有一点心得吧,分享一下,首先看数据,似乎可以暴力个部分分,每次选取一个点就开始模拟行走.每次都要寻找第一近和第二近,非常浪费时间,那么就预处理,怎么预处理呢?排序吗?有点复杂,离得最近的一定是高度差相近的,那么能排序并且在相近的元素中寻找的数据结构是什么呢?STL的set!每次找两个前驱和后继,然后排序取前两个.不过要注意,插入set要倒着插,因为每次都是去东边的城市.但是这样预处理之后还是不能通过所有的数据,我们来对算法仔细的分析,查找第一近和第二近的优化过了,枚举点似乎不能优化,那么只能优化模拟行走了.会发现两次行走可以合并成一次,有没有什么更快的枚举技巧呢?二分?似乎不行.那么就是倍增了.设f[i][j]为在位置i走2^j轮的位置,一轮就是A走和B走一次,fa[i][j]就是A在位置i走2^j轮的距离,fb[i][j]类同,那么怎么算呢?记住一个重要的性质:a^i-1 + a^i-1 = a^i,递推也可以这样,f[i][j] = f[f[i][j-1]][j-1],fa[i][j] = fa[i][j-1] + fa[f[i][j-1]][j-1],fb类同,因为n<=100000,所以2^i,i从19开始枚举,从大到小枚举,依次判断行不行即可.(真是复杂......)

      对枚举一个常见的优化:倍增.

1.储存方式

 1 struct sd{
 2     long long h,id;
 3 }node[100005];//记录每个城市的高度和编号,因为等会儿要排序找前驱后继 
 4 long long pre[100005],next[100005],hh[100005],da[100005][33],db[100005][33],fi[100005],se[100005],f[100005][33];
 5 //f[i][j] 从i地走j轮,会到哪个地方
 6 //da[i][j] 从i地走j轮,a要走多远 
 7 //db[i][j] 从i地走j轮,b要走多远 
 8 //fi(rst)[i] i地的最近点
 9 //se(cond)[i] i地的第二近点
10 //pre[i] i的前驱 next[i] i的后继 形成链表 
11 //hh[i] 记录高度 

2.对每个城市进行预处理

PS.这一步中,查找最近城市与第二近的城市的代码较为冗杂,可以写得更优化,但蒟蒻的我码力不够,就凑合着看吧,望大佬勿喷。。。

 1 void build(int v)
 2 {
 3     long long h[5];
 4     long long p1=pre[v];
 5     long long p2=pre[p1];
 6     long long n1=next[v];
 7     long long n2=next[n1];
 8     if(p1==0) h[1]=INF; else {h[1]=hh[v]-hh[p1];h[1]=h[1]>0?h[1]:-h[1];}
 9     if(p2==0) h[2]=INF; else {h[2]=hh[v]-hh[p2];h[2]=h[2]>0?h[2]:-h[2];}
10     if(n1==0) h[3]=INF; else {h[3]=hh[v]-hh[n1];h[3]=h[3]>0?h[3]:-h[3];}
11     if(n2==0) h[4]=INF; else {h[4]=hh[v]-hh[n2];h[4]=h[4]>0?h[4]:-h[4];}
12     //算出距离 
13     long long minn1=INF,minn2=INF,pp1=0,pp2=0;
14     if(minn1>h[1]) {minn1=h[1],pp1=p1;}
15     if(minn1>h[2]) {minn1=h[2],pp1=p2;}
16     if(minn1>h[3]) {minn1=h[3],pp1=n1;}
17     if(minn1>h[4]) {minn1=h[4],pp1=n2;}
18     //找出最近点 
19     if(minn2>h[1]&&pp1!=p1||(minn2==h[1]&&hh[pp2]>hh[p1])) {minn2=h[1],pp2=p1;}
20     if(minn2>h[2]&&pp1!=p2||(minn2==h[2]&&hh[pp2]>hh[p2])) {minn2=h[2],pp2=p2;}
21     if(minn2>h[3]&&pp1!=n1||(minn2==h[3]&&hh[pp2]>hh[n1])) {minn2=h[3],pp2=n1;}
22     if(minn2>h[4]&&pp1!=n2||(minn2==h[4]&&hh[pp2]>hh[n2])) {minn2=h[4],pp2=n2;}
23     //找出第二近点 
24     if(hh[pp1]>hh[pp2]&&minn1==minn2&&minn1!=INF)
25     {int gg=pp1;pp1=pp2;pp2=gg;}
26     if(minn1!=INF)
27     fi[v]=pp1;
28     if(minn2!=INF)
29     se[v]=pp2;
30     //删除链表上的点,因为是从东到西,所以先插入链表的西边的城市在后来不会被到达,所以删掉,以免出错 
31     if(p1!=0) next[p1]=n1;
32     if(n1!=0) pre[n1]=p1;
33 }

3.预处理da[i][j] , db[i][j] , f[i][j]

这一步比较好理解,在这里就不赘述了

 1 for(int i=1;i<=n;i++)
 2     {
 3         f[i][0]=fi[se[i]];
 4         if(i<=n-2)
 5         da[i][0]=abs(hh[i]-hh[se[i]]);
 6         if(i<=n-1)
 7         db[i][0]=abs(hh[se[i]]-hh[fi[se[i]]]);
 8     }
 9     
10     for (int i=1;i<=32;i++)
11     for (int j=1;j<=n;j++)
12     {
13         f[j][i]=f[f[j][i-1]][i-1];
14         da[j][i]=da[j][i-1]+da[f[j][i-1]][i-1];
15         db[j][i]=db[j][i-1]+db[f[j][i-1]][i-1];
16     }

3.开始跑路,求解问题一

 1 double minn=INF,minh=-1;
 2     long long ans=1;
 3     long long x;
 4     scanf("%lld",&x); 
 5     for(int i=1;i<=n;i++)
 6     {
 7         long long l=x;//记录长度,确保在跑了x后停止 
 8         long long point=i;//跑路的指针 
 9         long long A=0,B=0;//分别记录A,B的跑路距离 
10         for(int j=32;j>=0;j--)
11         {
12             if(f[point][j]!=0&&da[point][j]+db[point][j]<=l)//如果可以跑,那就跑呗 
13             {
14                 l-=da[point][j]+db[point][j]; 
15                 A+=da[point][j];
16                 B+=db[point][j];
17                 point=f[point][j];
18             }
19         }
20         if (da[point][0]<=l)//最后再特判一下A还能不能走,如果可以,那就让他单独走一波 
21         {
22             A+=da[point][0];
23             l-=da[point][0];
24         }
25         double C=INF;
26         if (B==0)
27             C=INF;
28         else
29             C=A*1.0/B;
30         //求比值 
31         if (C<minn||C==minn&&hh[i]>minh)
32         {
33             minn=C;
34             minh=hh[i];
35             ans=i;
36         }
37     }
38     if(minn!=INF) 
39     printf("%lld
",ans);
40     else
41     printf("%lld
",node[1].id);

4.继续跑路,求解问题二

这个问题的写法,与上面极为相似,只是多个循环枚举每个起点就行了,代码不难想

 1 long long m;
 2     scanf("%lld",&m);
 3     while(m--)
 4     {
 5         long long ss,xx;
 6         scanf("%lld%lld",&ss,&xx);
 7         long long l=xx;
 8         long long point=ss;
 9         long long A=0,B=0;
10         for(int j=32;j>=0;j--)
11         {
12             if(f[point][j]!=0&&da[point][j]+db[point][j]<=l)
13             {
14                 l-=da[point][j]+db[point][j];
15                 A+=da[point][j];
16                 B+=db[point][j];
17                 point=f[point][j];
18             }
19         }
20         if (da[point][0]<=l)
21         {
22             A+=da[point][0];
23             l-=da[point][0];
24         }
25         printf("%lld %lld
",A,B);
26     }

最后问题就解决了

这道题其实很有研究的价值,也算是倍增的一道经典题型了

倍增再一次体现了log线性处理的优越性,可以用于今后时间复杂度较迷的题目上。。。

原文地址:https://www.cnblogs.com/genius777/p/8481338.html