1. 程式人生 > >「IOI2018」Highway 高速公路收費

「IOI2018」Highway 高速公路收費

alt c++ 個人 做的 開始 讀取 數組 for amp

目錄

  • 「IOI2018」Highway 高速公路收費
  • 題目描述:
  • 實現細節:
  • 輸入格式:
  • 輸出格式:
  • 樣例:
  • 數據範圍與提示:
  • 子任務:
  • 題解:
  • Code

「IOI2018」Highway 高速公路收費

題目描述:

在日本,城市是用一個高速公路網絡連接起來的。這個網絡包含 \(N\) 個城市和 \(M\) 條高速公路。每條高速公路都連接著兩個不同的城市。不會有兩條高速公路連接相同的兩個城市。城市的編號是從 \(0\)\(N-1\) ,高速公路的編號則是從 \(0\)\(M-1\) 。每條高速公路都可以雙向行駛。你可以從任何一個城市出發,通過這些高速公路到達其他任何一個城市。

使用每條高速公路都要收費。每條高速公路的收費都會取決於它的交通狀況。交通狀況或者為順暢,或者為繁忙。當一條高速公路的交通狀況為順暢時,費用為 \(A\)

日元(日本貨幣),而當交通狀況為繁忙時,費用為 \(B\) 日元。這裏必有 \(A<B\) 。註意,\(A\)\(B\) 的值對你是已知的。

你有一部機器,當給定所有高速公路的交通狀況後,它就能計算出在給定的交通狀況下,在兩個城市 \(S\)\(T(S≠T)\)之間旅行所需要的最小的高速總費用。

然而,這臺機器只是一個原型。所以 \(S\)\(T\) 的值是固定的(即它已經被硬編碼到機器中),但是你並不知道它們的值是什麽。你的任務就是去找出 \(S\)\(T\) 的值。為了找出答案,你打算先給機器設定幾種交通狀況,然後利用它輸出的高速費用來推斷出 \(S\)\(T\)

。由於設定高速公路交通狀況的代價很大,所以你並不想使用這臺機器很多次。

實現細節:

你需要實現下面的過程:

void find_pair(int N, std::vector<int> U, std::vector<int> V, int A, int B)
  • \(N\): 城市的數量。
  • \(U\)\(V\) : 長度為 \(M\) 的數組,其中 \(M\) 為連接城市的高速公路的數量。對於每個 \((0 \leq i \leq M-1)\),高速公路 \(i\) 連接城市 \(U[i]\)\(V[i]\)
  • \(A\): 交通狀況順暢時高速公路的收費。
  • \(B\)
    : 交通狀況繁忙時高速公路的收費.

對於每個測試樣例,該過程會被調用恰好一次。
註意,\(M\) 為數組的長度,可以按照註意事項的相關內容來取得。

過程 \(find\)_\(pair\) 可以調用以下函數:

long long ask(std::vector<int> w)
  • w 的長度必須為 \(M\) 。 數組 \(w\) 描述高速公路的交通狀況。
  • 對於每個 \((0 \leq i \leq M-1)\)\(w[i]\) 表示高速公路 \(i\) 的交通狀況。 \(w[i]\) 的值必須為 \(0\)\(1\)

    • \(w[i] = 0\) 表示高速公路 \(i\) 的交通狀況為順暢。
    • \(w[i] = 1\) 表示高速公路 \(i\) 的交通狀況為繁忙。
  • 該函數返回的是,在 \(w\) 所描述的交通狀況下,在城市 \(S\)\(T\) 之間旅行所需的最少總費用。

  • 該函數最多只能被調用 \(100\) 次 (對於每個測試樣例)。

\(find\)_\(pair\) 應調用以下過程來報告答案 :

void answer(int s, int t)
  • \(s\)\(t\) 的值必須為城市 \(S\)\(T\)(兩者的先後次序並不重要)。
  • 該過程必須被調用恰好一次。

如果不滿足上面的條件,你的程序將被判為 \(Wrong Answer\) 。否則,你的程序將被判為
\(Accepted\),而你的得分將根據 \(ask\) 的調用次數來計算(參見子任務)。

輸入格式:

交互庫將讀取如下格式的輸入:

  • \(1\) 行:\(N\) \(M\) \(A\) \(B\) \(S\) \(T\)
  • \(i+2\)\((0 \leq i \leq M-1)\)\(U[i]\) \(V[i]\)

輸出格式:

如果你的程序被判為 \(Accepted\) ,交互庫打印出 \(Accepted:\) \(q\) ,這裏的 \(q\) 為函數 \(ask\)被調用的次數。
如果你的程序被判為 \(Wrong Answer\) ,它打印出 \(Wrong Answer:\) \(MSG\) 。各類 $MSG 的含義如下:

  • \(answered\) \(not\) \(exactly\) \(once\):過程 \(answer\) 沒有被調用恰好一次。
  • \(w\) \(is\) \(invalid\):傳給函數 \(ask\)\(w\) 的長度不是 \(M\) ,或者某個 \(i\) \((0 \leq i \leq M-1)\) 上的 \(w[i]\) 既不是 \(0\) 也不是 \(1\)
  • \(more\) \(than\) \(100\) \(calls\) \(to\) \(ask\):函數 \(ask\) 的調用次數超過 \(100\) 次。
  • \({s, t}\) \(is\) \(wrong\):調用 \(answer\) 時的 \(s\)\(t\) 是錯的。

樣例:

\(N=4\),\(M=4\),\(U=[0,0,0,1]\),\(V=[1,2,3,2]\),\(A=1\),\(B=3\),\(S=1\)\(T=3\) 。評測程序調用 \(find\)_\(pair(4, \{0, 0, 0, 1\}, \{1, 2, 3, 2\}, 1, 3)\)

技術分享圖片

上圖中,編號為 的 \(i\) 邊對應高速公路 \(i\) 。其中一些對 \(ask\) 的可能調用和對應的返回值如下表所示:

\[ \begin{matrix} 調用 & & & & & & & & & 返回值 \\ ask(\{0,0,0,0\}) & & & & & & & & & 2 \ask(\{0,1,1,0\}) & & & & & & & & & 4 \ask(\{1,0,1,0\}) & & & & & & & & & 5 \ask(\{1,1,1,1\}) & & & & & & & & & 6 \end{matrix} \]

對於函數調用 \(ask(\{0, 0, 0, 0\})\) ,所有高速公路的交通狀況均為順暢,因此每條高速公路的費用都是 \(1\) 。從城市 \(S=1\) 到城市 \(T=3\) 的費用最低的路徑就是 \(1 \to 0 \to 3\)。這條路徑的總費用等於 \(2\) 。因此,這個函數的返回值就是 \(2\)

對於一個正確的解答來說,過程 \(find\)_\(pair\) 應調用 \(answer(1, 3)\)\(answer(3, 1)\)

數據範圍與提示:

  • \(2 \leq N \leq 90000\)
  • \(1 \leq M \leq 130000\)
  • \(1 \leq A \leq B \leq 1000000000\)
  • 對於每個\(0 \leq i \leq M-1\)
    • \(0 \leq U[i] \leq N-1\)
    • \(0 \leq V[i] \leq N-1\)
    • \(U[i] \neq V[i]\)
  • \((U[i],V[i]) \neq (U[j],V[j])且(U[i],V[i]) \neq (U[j],V[j]) (0<i<j \leq M-1)\)
  • 你可以從任何一個城市出發,通過高速公路到達其他任何一個城市。
  • \(0 \leq S \leq N-1\)
  • \(0 \leq T \leq N-1\)
  • \(S \neq T\)
    在本題中,評測程序不是適應性的。意思是說,在評測程序開始運行的時候 SSS 和 TTT 就固定下來,而且不依賴於你的程序所做的詢問。

子任務:

\[ \begin{matrix} 編號 & 分值 & & & & & & & 限制 \1 & 5 & & & & & & & S 或 T 有一個是 0,N \leq 100, M = N-1 \2 & 7 & & & & & & & S 或 T 有一個是 0,M = N-1 \3 & 6 & & & & & & & M=N-1,U[i]=i,V[i]=i+1(0 \leq i \leq M-1) \4 & 33 & & & & & & & M=N-1 \5 & 18 & & & & & & & A=1,B=2 \6 & 31 & & & & & & & 沒有附加限制 \\end{matrix} \]

假設你的程序被判為 \(Accepted\),而且函數 \(ask\) 調用了 \(X\) 次。你在該測試樣例上的得分 \(P\) ,取決於對應子任務的編號,其計算如下:

  • 子任務 1 :\(P=5\)
  • 子任務 2 :如果 \(X \leq 60\)\(P=7\) 。否則 \(P=0\)
  • 子任務 3 :如果 \(X \leq 60\)\(P=6\) 。否則 \(P=0\)
  • 子任務 4 :如果 \(X \leq 60\)\(P=33\) 。否則 \(P=0\)
  • 子任務 5 :如果 \(X \leq 52\)\(P=18\) 。否則 \(P=0\)
  • 子任務 6 :
    • 如果 \(X \leq 50\)\(P=31\)
    • 如果 \(51 \leq X \leq 52\)\(P=21\)
    • 如果 \(53 \leq X\)\(P=0\)

註意,你在每個子任務上的得分,等於你在該子任務中所有測試樣例上的最低得分。

題解:

一道被機房dalao Dance-Of-Faith A穿了的IOI題。個人認為這個是一道非常好的題目,在做著子任務的時候,慢慢的標算就可以推出來了,做出來之後發現也不是特別毒瘤,做起來也是十分享受的。所以我們懟著子任務一個一個做過去吧。

子任務1:

這個應該不用多講吧,由於邊數最多只有\(99\)條,並且這是一棵樹,所以我們可以先詢問一次,得到\(S\)\(T\)的距離之後一條一條邊詢問過去即可。

子任務2:

這個相比於子任務1不同的地方在於這棵樹的邊數會有很多,所以並不能一條一條詢問過去。於是我們開始想給出了\(S\)\(T\)有一個點在\(0\)的條件有什麽用。首先,我們還是需要詢問一次來得到\(S\)\(T\)之間的距離\(dist\),然後因為一個點在\(0\),那麽我們就可以確定另一個點是在以這個點為根的樹中深度為\(dist\)之間的節點中的一個。然後就是很好想了,我們把可能的節點全部都取出來,然後在這些點之中二分。假設當前的區間是\([l,r]\),那麽我們將\([l,mid]\)之中所有的點到根節點之間的路徑全部都設置成\(1\),這樣,如果得到的回答仍然還是\(dist*A\)則說明\([l,mid]\)區間沒有所求的點,繼續在\([mid+1,r]\)之間二分,否則在\([l,mid]\)之間二分,這樣就可以得到另一個節點的位置了,最多的詢問次數為\(log(n)+1=18\)次。

子任務3:

這個應該沒有什麽好講的吧,就是一條鏈上二分搞搞就可以了。

子任務4:

這個子任務是最接近正解的一個了吧,題目中只保證這是一顆樹。想一想會發現,如果我們兩個點的位置都不知道的話,實際上是做不出來的。所以我們盡量的將這個子任務轉化成之前的比較簡單的子任務。實際上,雖然題目沒有保證\(S\)\(T\)中至少一個是\(0\),但是我們可以構造出這樣的一種情況,即確定了某條邊\(e=u \to v\)是從\(S\)\(T\)之間的一條邊,然後把這條邊割掉,分成兩個樹,在以\(u\)為根的子樹中找\(S\),以\(v\)為根的子樹中找\(T\)。這樣我們就相當於把這轉化成了子任務2了,之後的做法都是一樣的。最多的詢問次數是\(log(m)+2*log(\frac{n}{2})=17+16*2=49\)次詢問。

子任務5:

這。。就跳過了吧。。雖然並不知道這個子任務到底怎麽做,或許有高妙的做法吧,但是我們通過前幾個子任務實際上就可以推出標算了。

子任務6:

現在我們要在一個圖中考慮這道題目了。實際上,之前的子任務已經提示很多了,既然之前的子任務都是在樹中完成的,那麽我們就會想到實際上這道題目或許在樹中比較容易做一些。於是我們還是考慮把這幅圖轉化成一棵樹,然後按照子任務4的做法就可以完成了。但是我們需要考慮到一點,就是我們如果把這幅圖轉成樹,\(S\)\(T\)的最短花費可能就會改變。實際上,我們只需要找到一條邊\(e=u \to v\),使得\(e\)至少在一條從\(S\)\(T\)的最短路上,然後分別一\(u\)\(v\)為根,兩邊形成兩棵\(Bfs\)樹,這樣就可以轉化成子任務4了。為什麽呢?首先我們找到了這樣一條邊\(e\),並且形成了\(Bfs\)樹之後,我們就可以把所有的非樹邊都染成\(B\),這樣從\(S\)\(T\)的最少花費的路徑一定是在這個樹上的。於是我們就相當於強制把這個圖轉化成了一棵樹了。之後和子任務4的做法就是一樣的。所以接下來具體的講一講如何找到一條這樣的邊\(e\)
首先我們仍然采用二分的方法。假設我們當前二分到了區間\([l,r]\),並且\(S\)\(T\)的最小花費仍然是\(dist*A\)。然後我們嘗試將\([l,mid]\)的所有的邊染成\(B\),如果這樣查詢之後的最小花費不再是\(dist*A\),那麽在\([l,mid]\)區間中至少有一條邊處於從\(S\)\(T\)的最短路上的,於是我們就繼續在\([l,mid]\)中二分,否則在\([mid+1,r]\)中二分。

引用一段來自Dance_Of_Faith的講解:

但是這裏我還是想要講一下細節上存在的問題,也就是染\(A\)\(B\)交換什麽時候會出問題。在樹上做的時候完全不會有問題,我前面說了,因為樹上的最短路徑是唯一的。但圖上我們始終堅持保留一條全\(A\)的路徑,也就是讓判定條件是\(dist*A\),原因很簡單,如果不這麽做會導致另一條不一定是最短路的路徑成為當前染色情況下的帶權最短路。
舉一個在找\(e=u \to v\)時的例子,假設\(A<<B\)\(s\)\(t\)有兩條長為\(2,3\)的路徑,一開始所有邊都是\(B\):如果我們第一次二分把長度為\(3\)的路徑染為\(A\),得到的回答會是\(3*A\),也就是走了長度為\(3\)的路,我們無法從中知道這些染成\(A\)中的邊中是否一定存在最短路上的邊。以及在\(Bfs\)樹上二分的時候,如果調換\(A,B\)的角色,只把樹邊染成\(B\),每次二分的時候把樹上的二分範圍以外的邊染成\(A\),問題就會和上個例子類似。實際結果就是詢問得到的最短路有可能是從橫跨兩棵樹的邊上繞過去的,而並沒有經過\(u \to v\),因為一個點可能從它的子樹中繞出去會更短,具體例子在這裏就不做說明了。

最後計算一下最多需要的詢問次數:
1.剛開始需要一次詢問來得到\(dist\)
2.二分得到邊\(e\),需要\(log(m)=17\)次。
3.在每棵樹上二分,最多需要\(2*log(n)=16*2=32\)次。

所以這樣最多只需要\(50\)次就可以完成任務,並且這實際上並不會被卡成這樣,所以知道算法之後還是比較容易過的。

Code

#include "highway.h"
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
typedef pair<int,int>P;
const int N=1e5+500;
#define fi first
#define se second
#define mk make_pair
std::vector<int>w,T1,T2;
vector<P>G[N];
int disu[N],disv[N],idxu[N],idxv[N];
int n,m,pos;
ll res,dist;

int GetPath() {
  fill(w.begin(),w.end(),0);
  int l=0,r=m-1;
  while(l<r) {
    int mid=(l+r)>>1;
    for(int i=l;i<=mid;i++) w[i]=1;
    if(ask(w)==res) l=mid+1;
    else {
      for(int i=l;i<=mid;i++) w[i]=0;
      r=mid;
    }
  }
  return l;
}

void Bfs(int st,int *dis,int *idx) {
  queue<int>Q;
  while(!Q.empty()) Q.pop();
  for(int i=0;i<n;i++) dis[i]=-1;
  Q.push(st);
  dis[st]=0;
  while(!Q.empty()) {
    int o=Q.front();Q.pop();
    for(auto p:G[o]) {
      int to=p.fi,id=p.se;
      if(dis[to]==-1) {
    dis[to]=dis[o]+1;
    idx[to]=id;
    Q.push(to);
      }
    }
  }
}

int Solve(vector<int>&In,vector<int>&Ot,int *idxIn,int *idxOt) {
  int l=0,r=In.size()-1;
  while(l<r) {
    int mid=(l+r)>>1;
    for(int i=0;i<m;i++) w[i]=1;
    w[pos]=0;
    for(int i=1;i<(int)Ot.size();i++)  w[idxOt[Ot[i]]]=0;
    for(int i=1;i<=mid;i++) w[idxIn[In[i]]]=0;
    ll ret=ask(w);
    if(ret==res) r=mid;
    else l=mid+1;
  }
  return In[l];
}

void find_pair(int N,std::vector<int> U,std::vector<int> V,int A,int B) {
  n=N,m=U.size();
  for(int i=0;i<m;i++) w.push_back(0);
  for(int i=0;i<m;i++) {
    G[U[i]].push_back(mk(V[i],i));
    G[V[i]].push_back(mk(U[i],i));
  }
  res=ask(w);
  dist=res/A;
  pos=GetPath();
  Bfs(U[pos],disu,idxu);
  Bfs(V[pos],disv,idxv);
  for(int i=0;i<n;i++) {
    if(disu[i]<disv[i]) T1.push_back(i);
    if(disv[i]<disu[i]) T2.push_back(i);
  }
  sort(T1.begin(),T1.end(),[](int x,int y) {return disu[x]<disu[y];});
  sort(T2.begin(),T2.end(),[](int x,int y) {return disv[x]<disv[y];});
  int pos1=Solve(T1,T2,idxu,idxv);
  int pos2=Solve(T2,T1,idxv,idxu);
  answer(pos1,pos2);
  return ;
} 

「IOI2018」Highway 高速公路收費