1. 程式人生 > >【NOIP2012】開車旅行

【NOIP2012】開車旅行

algo sam time mat row 不同的 blog -- 但是

Description

小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 行駛的路程總數。

Input

第一行包含一個整數 N,表示城市的數目。
第二行有 N 個整數,每兩個整數之間用一個空格隔開,依次表示城市 1 到城市 N 的海拔高度,即H1,H2,……,Hn,且每個Hi都是不同的。
第三行包含一個整數 X0。
第四行為一個整數 M,表示給定M組Si和 Xi。
接下來的M行,每行包含2個整數Si和Xi,表示從城市 Si出發,最多行駛Xi公裏。

Output

輸出共M+1 行。
第一行包含一個整數S0,表示對於給定的X0,從編號為S0的城市出發,小A開車行駛的路程總數與小B行駛的路程總數的比值最小。
接下來的 M 行,每行包含 2 個整數,之間用一個空格隔開,依次表示在給定的 Si和Xi下小A行駛的裏程總數和小B 行駛的裏程總數。

Sample Input

樣例1:
4
2 3 1 4
3
4
1 3
2 3
3 3
4 3
樣例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

Sample Output

樣例1:
1
1 1
2 0
0 0
0 0
樣例2:
2
3 2
2 4
2 1
2 4
5 1
5 1
2 1
2 0
0 0
0 0

Hint

樣例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≤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互不相同。

求2012年選手們的心理陰影面積。。。這題%%了XLightGod的代碼好久。。。

這個題最直接的想法就是對於每個點求出在這個點小A和小B會走到哪個位置(相當於是求後面的點中的最接近元素與第二接近元素);

可以逆著插入set中然後查詢當前set中的該元素的前驅和前驅的前驅以及後繼和後繼的後繼來更新最接近和第二接近的點(答案只有可能在這四個點中取兩個)

由於有眾多條件說明,所以寫得特別操蛋,但本蒟蒻選手不會用set。。。所以學了好久,下面有set的說明;

求出來之後,這種序列上移動的問題一般可以考慮倍增,

我們可以發現小A和小B是輪流操作的,於是我們可以設計狀態

f[j][i]表示小車從i出發交換了2^j次所在的點;

step[j][i][1]表示從i出發交換了2^j後,移動的距離;

step[j][i][0]表示從i出發交換了2^j後,移動的距離;

其余數組:qianqu[i][0]表示i的小B走到的點(第一接近),qianqu[i][1]表示i的小A走到的點(第二接近),

初始化的話:

f[0][i]=qianqu[qianqu[i][1]][0];(最後停下來的點是小A開完後小B開到的地方)
step[0][i][1]=dis[i][1];
step[0][i][0]=dis[qianqu[i][1]][0];

其余的就可以正常的倍增就可以了。。。記住因為狀態表示的是多少輪後,所以最後小A還可能自己獨自走一次。。。

第一問就每個點暴枚,第二問操作一樣;

// MADE BY QT666
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
const int N=300050;
set<int> q;
set<int>::iterator it;
map<int,int> mp;
int gi()
{
  int x=0,flag=1;
  char ch=getchar();
  while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) flag=-1;ch=getchar();}
  while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar();
  return x*flag;
}
int h[N],qianqu[N][2],dis[N][2],f[20][N],step[20][N][2],n;
void update(int x,int y){
  int a=mp[x],b=mp[y],d=abs(x-y);
  if(!qianqu[a][1]||d<dis[a][1]||(d==dis[a][1]&&y<h[qianqu[a][1]])){
    qianqu[a][1]=b;
    dis[a][1]=d;
    if(!qianqu[a][0]||dis[a][1]<dis[a][0]||(dis[a][1]==dis[a][0]&&y<h[qianqu[a][0]])){
      swap(qianqu[a][0],qianqu[a][1]);
      swap(dis[a][0],dis[a][1]);
    }
  }
}
void pre(){
  for(int i=n;i;i--){
    q.insert(h[i]);
    it=q.find(h[i]);
    if(it!=q.begin()){
      it--;
      update(h[i],*it);
      if(it!=q.begin()){it--;update(h[i],*it);it++;}
      it++;
    }
    if((++it)!=q.end()){
      update(h[i],*it);
      if((++it)!=q.end())update(h[i],*it);
    }
  }
}
struct data{
  int a,b;
};
data query(int st,int time){
  int a=0,b=0;
  for(int i=17;i>=0;i--){
    if(f[i][st]&&step[i][st][1]+step[i][st][0]<=time){
      time=time-(step[i][st][1]+step[i][st][0]);
      a+=step[i][st][1],b+=step[i][st][0];
      st=f[i][st];
    }
  }
  if(qianqu[st][1]&&dis[st][1]<=time) a+=dis[st][1];
  return (data){a,b};
}
int main(){
  n=gi();for(int i=1;i<=n;i++) mp[h[i]=gi()]=i;
  pre();
  for(int i=1;i<=n;i++){
    f[0][i]=qianqu[qianqu[i][1]][0];
    step[0][i][1]=dis[i][1];
    step[0][i][0]=dis[qianqu[i][1]][0];
  }
  for(int j=1;j<=17;j++)
    for(int i=1;i<=n;i++){
      f[j][i]=f[j-1][f[j-1][i]];
      step[j][i][1]=step[j-1][i][1]+step[j-1][f[j-1][i]][1];
      step[j][i][0]=step[j-1][i][0]+step[j-1][f[j-1][i]][0];
    }
  int X0=gi(),ans2=0;double ans1=2147483647.0;
  for(int i=1;i<=n;i++){
    data ans=query(i,X0);double ret;
    if(ans.b==0) ret=2147483647.0;
    else ret=ans.a/(double)ans.b;
    if(ret<ans1||(h[ans2]<h[i]&&ret==ans1)) ans1=ret,ans2=i;
  }
  printf("%d\n",ans2);
  int m=gi();
  for(int i=1;i<=m;i++){
    int S=gi(),X=gi();
    data ans=query(S,X);
    printf("%d %d\n",ans.a,ans.b);
  }
  return 0;
}

註set操作說明:

q.insert(x)在set中插入x;

set<int>::iterator it;表示一個指針,set函數的返回值大部分是指針;

it=q.find(x):

1.在set中查找x,返回x在set中的位置,

2.*it表示這個位置上的值;

3.it--表示這個數的前驅,it++表示這個數的後繼

4.q.begin(),set中的第一個(判斷是否有前驅用),q.end(),set中的最後一個(判斷是否有後繼用);

【NOIP2012】開車旅行