[USACO08JAN] Telephone Lines S
題目描述
Farmer John wants to set up a telephone line at his farm. Unfortunately, the phone company is uncooperative, so he needs to pay for some of the cables required to connect his farm to the phone system.
There are N (1 ≤ N ≤ 1,000) forlorn telephone poles conveniently numbered 1..N that are scattered around Farmer John's property; no cables connect any them. A total of P (1 ≤ P ≤ 10,000) pairs of poles can be connected by a cable; the rest are too far apart.
The i-th cable can connect the two distinct poles Ai and Bi, with length Li (1 ≤ Li ≤ 1,000,000) units if used. The input data set never names any {Ai, Bi} pair more than once. Pole 1 is already connected to the phone system, and pole N is at the farm. Poles 1 and N need to be connected by a path of cables; the rest of the poles might be used or might not be used.
As it turns out, the phone company is willing to provide Farmer John with K (0 ≤ K < N) lengths of cable for free. Beyond that he will have to pay a price equal to the length of the longest remaining cable he requires (each pair of poles is connected with a separate cable), or 0 if he does not need any additional cables.
Determine the minimum amount that Farmer John must pay.
多年以後,笨笨長大了,成為了電話線佈置師。由於地震使得某市的電話線全部損壞,笨笨是負責接到震中市的負責人。該市周圍分佈著 \(1\le N\le1000\) 根據 \(1\cdots N\) 順序編號的廢棄的電話線杆,任意兩根線杆之間沒有電話線連線,一共有 \(1\le p\le10000\) 對電話杆可以拉電話線。其他的由於地震使得無法連線。
第i對電線杆的兩個端點分別是 \(a_i\) ,\(b_i\),它們的距離為 \(1\le l_i\le1000000\)。資料中每對 \((a_i,b_i)\) 只出現一次。編號為 \(1\) 的電話杆已經接入了全國的電話網路,整個市的電話線全都連到了編號 \(N\) 的電話線杆上。也就是說,笨笨的任務僅僅是找一條將 \(1\) 號和 \(N\) 號電線杆連起來的路徑,其餘的電話杆並不一定要連入電話網路。
電信公司決定支援災區免費為此市連線 \(k\) 對由笨笨指定的電話線杆,對於此外的那些電話線,需要為它們付費,總費用決定於其中最長的電話線的長度(每根電話線僅連線一對電話線杆)。如果需要連線的電話線杆不超過 \(k\) 對,那麼支出為 \(0\)。
請你計算一下,將電話線引導震中市最少需要在電話線上花多少錢?
輸入格式
輸入檔案的第一行包含三個數字 \(n,p,k\)。
第二行到第 \(p+1\) 行,每行分別都為三個整數 \(a_i,b_i,l_i\)。
輸出格式
一個整數,表示該項工程的最小支出,如果不可能完成則輸出 -1
。
樣例 #1
樣例輸入 #1
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
樣例輸出 #1
4
P1948 [USACO08JAN] Telephone Lines S
分析
\(x\) 是要求的最小化最大值的答案。
要使 \(x\) 成立,從 \(1\) 到 \(n\) 的路徑中大於x的邊全部要刪掉。
因此大於x的邊的數量必須要小於k。
最小化最大值,考慮二分:
\(num(x)\) 表示從 \(1\) 到 \(n\) 的路徑中大於 \(x\) 的邊的最小數量。
在區間 \([l,r]\) 上滿足 \(num(x)=k\)。
-
當存在 \(y>r\)
易證明,\(num(y)<num(x)\),因此 \(num(y)<k\)。 -
當存在 \(y<l\)
\(num(y)>num(x)\),\(num(y)>k\)。
因此二分確定區間,再取區間的最小值,當然是邊權存在情況下的最小值,就可以得到答案。
二分割槽間為 \([0,1e6+1]\)。
具體來說:
對於一個滿足 \(num(x)<=k\) 的 \(x\):
- \(y>x\) 時,雖然滿足 \(num(y)<=k\),但一定不滿足最小,捨去。
- \(y<x\) 時,可能滿足 \(num(y)<=k\),此時答案 \(x\) 就更新為 \(y\)。
對於一個滿足 \(num(x)>k\) 的 \(x\):
- \(y<x\) 時,一定有 \(num(y)>=num(x)>k\),不符合條件,捨去。
- \(y>x\) 時,\(num(y)<=num(x)\),可能滿足 \(num(y)<=k\),此時答案 \(x\) 就更新為 \(y\)。
這就是二分的完整過程。
tips
為何二分割槽間為 \([0,1e6+1]\)?
- 對於左端點 \(0\),當從 \(1\) 到 \(n\) 的路徑經過的邊的數量小於 \(k\) 答案就是 \(0\)。
- 對於右端點 \(1e6+1\),\(1\) 無法到達 \(n\) 時,\(1e6+1\) 作為無解的返回值。
如何求 \(num(x)\)?
如果邊長大於 \(x\),邊權看作 \(1\),否則看作 \(0\)。
接下來求從 \(1\) 到 \(n\) 的最短路徑,不過這裡可以不用最短路演算法,邊權為 \(1\) 或 \(0\),可以直接使用 \(bfs+雙端佇列\)。
code
#include <bits/stdc++.h>
using namespace std;
const int N=1005,M=2e5+5;
int n,m,k;
int h[N],e[M],w[M],ne[M],tot;
deque<int> q;
int dis[N];
bool vis[N];
void add(int x,int y,int z)
{
e[++tot]=y,w[tot]=z,ne[tot]=h[x],h[x]=tot;
}
bool check(int x)
{
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
dis[1]=0;
q.push_back(1);
while(q.size())
{
int u=q.front();q.pop_front();
if(vis[u]) continue;
vis[u]=1;
for(int i=h[u];i;i=ne[i])
{
int y=e[i],z=w[i]>x;
if(dis[y]>dis[u]+z)
{
dis[y]=dis[u]+z;
if(z) q.push_back(y);
else q.push_front(y);
}
}
}
return dis[n]<=k;
}
int main ()
{
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
add(x,y,z),add(y,x,z);
}
int l=0,r=1e6+1;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
if(r==1e6+1) r=-1;
cout<<r<<"\n";
return 0;
}