1. 程式人生 > >hihoCoder 1139 二分·二分答案

hihoCoder 1139 二分·二分答案

二分·二分答案

在上一回和上上回裡我們知道Nettle在玩《艦これ》,Nettle在整理好艦隊之後終於準備出海撈船和敵軍交戰了。
在這個遊戲裡面,海域是N個戰略點(編號1..N)組成,如下圖所示

其中紅色的點表示有敵人駐紮,貓頭像的的點表示該地圖敵軍主力艦隊(boss)的駐紮點,虛線表示各個戰略點之間的航線(無向邊)
在遊戲中要從一個戰略點到相鄰戰略點需要滿足一定的條件,即需要艦隊的索敵值大於等於這兩點之間航線的索敵值需求。
由於提高索敵值需要將攻擊機、轟炸機換成偵察機,艦隊索敵值越高,也就意味著艦隊的戰力越低。
另外在每一個戰略點會發生一次戰鬥,需要消耗1/K的燃料和子彈。必須在燃料和子彈未用完的情況下進入boss點才能與boss進行戰鬥,所以艦隊最多隻能走過K條航路。


現在Nettle想要以最高的戰力來進攻boss點,所以他希望能夠找出一條從起始點(編號為1的點)到boss點的航路,使得艦隊需要達到的索敵值最低,並且有剩餘的燃料和子彈。

特別說明:兩個戰略點之間可能不止一條航線,兩個相鄰戰略點之間可能不止一條航線。保證至少存在一條路徑能在燃料子彈用完前到達boss點。

輸入

第1行:4個整數N,M,K,T。N表示戰略點數量,M表示航線數量,K表示最多能經過的航路,T表示boss點編號, 1≤N,K≤10,000, N≤M≤100,000
第2..M+1行:3個整數u,v,w,表示戰略點u,v之間存在航路,w表示該航路需求的索敵值,1≤w≤1,000,000。

輸出

第1行:一個整數,表示艦隊需要的最小索敵值。

樣例輸入

5 6 2 5
1 2 3
1 3 2
1 4 4
2 5 2
3 5 5
4 5 3

樣例輸出

3

解題思路:

在這個題目中我們需要找的是路徑最長邊。比如存在一條路徑{1, p[1], p[2], ... , p[j], T}, p = {p[1],p[2],...,p[j]]}, j < K,我們需要找的為 D(P) = Max{w(1, p[1]), w(p[j], T), Max{w(p[i], p[i+1]) | 1 <= i < j} }。則這道題的結果為找出所有從1到T的路徑P',求的Min{D(P')}。由於給定的圖存在環,所以要枚舉出所有1到T的路徑是很難的,因此我們需要換個角度去思考這個問題。

這道題結果有什麼特殊性?
不妨假設答案為j,如果艦隊滿足j以上的索敵值,那麼一定存在至少一條路徑可以從1到T,並且路徑數量小於K。
並且如果艦隊索敵值小於j,則在K條路徑的條件下一定無法從1到T。否則j就不是最小值了。
則對於索敵值滿足這樣一個關係:

可以看出,j值剛好是是否存在路徑的一個分界線。如果我們列舉一個j':

  • j'<j,無法到達boss點

  • j'>=j,一定可以到達boss點

則如何快速的找到這個分界線j,就是解決這道題目的關鍵。

不妨設f(x) = true(索敵值為x時,可以達到boss點), false(索敵值為x時,不能達到boss點)
1.從小到大列舉j',當出現第一個f(k')=true時,j'=j。需要列舉j次,若j很大時,該演算法很低效。
2.二分列舉,設定列舉區間[L,R],滿足f(L)=false, f(R)=true。每次取中間值Mid=(L+R)/2,若f(Mid)=true,令R=Mid;否則令L=Mid。
當L+1=R時,可以知道R即為我們需要尋找的j。

AC程式碼:

#include <bits/stdc++.h>
using namespace std;

struct edge{
    int x,w;
};
int n,m,k,t;
vector<edge> v[10005];
int vis[10005];

bool bfs(int dis){
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(1);
    while(!q.empty()){
        int cur = q.front();
        q.pop();
        if(cur == t)
            return true;
        if(vis[cur] == k)
            continue;
        int l = v[cur].size();
        for(int i = 0; i < l; i++){
            int tmp = v[cur][i].x;
            if(vis[tmp] || v[cur][i].w > dis)
                continue;
            vis[tmp] = vis[cur]+1;
            q.push(tmp);
        }
    }
    return false;
}

int main(){
    while(~scanf("%d%d%d%d",&n,&m,&k,&t)){
        int a,b,c,maxn = 0;
        for(int i = 0; i < m; i++){
            scanf("%d%d%d",&a,&b,&c);
            edge tmp;
            tmp.x = b;tmp.w = c;
            v[a].push_back(tmp);
            tmp.x = a;
            v[b].push_back(tmp);
            maxn = max(maxn,c);
        }
        int mid,l = 0,r = maxn;
        while(l <= r){
            mid = (l+r)>>1;
            if(bfs(mid))
                r = mid-1;
            else
                l = mid+1;
        }
        printf("%d\n",l);
    }
    return 0;
}