1. 程式人生 > >2069)すぬけ君の地下鉄旅行 / Snuke's Subway Trip(最短路拆點建圖思路'剖解'分析+想象法~)

2069)すぬけ君の地下鉄旅行 / Snuke's Subway Trip(最短路拆點建圖思路'剖解'分析+想象法~)

前言

做了一下午的真心奉獻

題目

Time limit : 3sec / Memory limit : 256MB

Score : 600 points

Problem Statement
Snuke’s town has a subway system, consisting of N stations and M railway lines. The stations are numbered 1 through N. Each line is operated by a company. Each company has an identification number.

The i-th ( 1≤i≤M ) line connects station pi and qi bidirectionally. There is no intermediate station. This line is operated by company ci.

You can change trains at a station where multiple lines are available.

The fare system used in this subway system is a bit strange. When a passenger only uses lines that are operated by the same company, the fare is 1 yen (the currency of Japan). Whenever a passenger changes to a line that is operated by a different company from the current line, the passenger is charged an additional fare of 1 yen. In a case where a passenger who changed from some company A’s line to another company’s line changes to company A’s line again, the additional fare is incurred again.

Snuke is now at station 1 and wants to travel to station N by subway. Find the minimum required fare.
Print the minimum required fare. If it is impossible to get to station N by subway, print -1 instead.

題目連結

題目連結

題目大意

現在有個人要從1~n,給出了這n個點之間的m條雙向邊,這m條邊每條邊有一屬性c,當你要經過一條邊時你的屬性要跟這條邊一樣,你要消耗一次次數改變自己屬性來通過這條邊,而你連續通過多條與你屬性相符的邊不耗費次數,求由1到n需要改變的最少次數(初始自身無屬性),無法聯通輸出-1

資料範圍

2≤N≤10^5
0≤M≤2×10^5
1≤pi≤N (1≤i≤M)
1≤qi≤N (1≤i≤M)
1≤ci≤10^6 (1≤i≤M)
pi≠qi (1≤i≤M)

思路

建圖方法

一定是學了最短路後再來看這道題的啊!
此方法是為了構圖跑最短路(SPFA,Dijstra堆優化都可以)
你可以給每個實點增加許多個虛點,然後需要你想象一下(imagine!),這是一個上下關係的類似於三維的圖,每個實點下面連了許多虛點,就像以前路邊賣氣球的婆婆手中抓著一大把氣球,但現在這些氣球是倒著的,但每一把氣球處於同一水平面(不會起伏)都連著婆婆的手,現在婆婆的手是不是就是實點?那些氣球是不是都是虛點??一個點就這麼幹,一個虛點c含義是與這個點相連的一邊屬性為c,
(Like this):
改變前:
拿氣球
改變後:
倒拿氣球
抽象出來大概長這個樣:
抽象模型
至於到底怎麼處理,我們先放一邊,等一下說,先看看,一個實點與另一實點怎麼建立關係?
我們可以知道,如果一個點在同一屬性的一個類似於網的東西中(~哼哼~),是可以亂走的,因為不消耗次數,而我們不能直接實點與實點相連,因為這樣是錯的(廢話!)
所以,如果一個點u與另一點v相連,其邊屬性為c,那我們要把實點u對應的虛點uc,實點v對應的虛點vc之間連一條邊,那麼如果有許多同一屬性的邊在連一起,是不是就形成了一個線路網一樣的東西?你還可以在裡面亂走?我們再來想象一下(imagine!),現在圖分為上下兩層上層是實點(上面各點之間毛都沒有),而由上到下,各實點像倒拿氣球一樣,與各自虛點相連,而屬於同一屬性的虛點間彼此形成了網,你一個一個屬性看網(不要一起看所有的虛邊),是不是很有美感?(不要廢話!)
來來來,你肯定懂了(我沒懂!!),舉個例子:
如下圖(是不是史上最好靈魂畫手?)俯檢視:
圖
它的上層:
上層
它的下層(還沒連邊的):
下層未連邊

來來來,連一個c=1(就是屬性為1形成的網)
連一條邊

來來來,連個2:
連個2
三就不打了
你一定要把它想象一下(imagine!),就像氣球、網路一樣
好了構圖樣子差不多了,關於權值的問題來了,怎麼分權值??
想想,同一網路中是不是可以亂走??那麼同一屬性所構成的網中虛點與虛點之間是不是就都是0
那由一個網路到另一網路就是換屬性,我們要利用實點與虛點這條邊,不對應該是一個虛點->實點->另一個虛點的這些路徑,把他們權值都設為1
來來來,你肯定懂了(。。。我不懂),看一下圖,恩,正檢視吧?
正檢視

也就是說,你跑最短路的意象圖是:鑽到下層,瘋狂亂跑,又鑽上來,又鑽到下層,瘋狂亂跑,又鑽上來…一直到終點
最後注意你是下上算一次換屬性,那你最後的dis[T]要除以2,(dis[i](1≤i≤n)肯定都為偶數)

關於實現

那麼問題來了,怎麼存虛點?
如果每個實點抵著資料開10^6(屬性最大)個虛點的話,那麼又有N個實點,我們來算算,10^5*10^6=10^11,呵呵,所以我們並不能這麼幹.
那我們怎麼辦呢??作者機智的思路是用Vector來處理構造虛點的.我令zx表示處理實點連虛點(z(真)x(虛),名字是不是很好?),用xx處理虛點連虛點(x(虛)x(虛),……),zx存的是(實點,所連虛點屬性),那麼你讀入的一條邊兩個端點(p,q),屬性(c),然後分別把邊拆成兩個一點加一屬性,push進zx裡就可以了,其實這是為了後面xx所準備的.
這是你sort一下zx,其cmp是端點為第一關鍵字,屬性為第二關鍵字(就是按端點從小到大,端點相等按屬性從小到大),(這都是為xx所準備),注意,zx中可能會Push進許多重複的元素,你可以換其他的資料結構實現。
你遍歷一下zx,那麼你就可以從(n~n+zx.size()-1)給每個虛點編號了,實點都和對應虛點相連了,比如之前那一個俯檢視,它的zx sort後長這樣:(實點編號,虛點屬性)(1,1)(1,1)(1,1)(2,1),(2,2),(2,3),(3,1),(3,2),(3,2),(3,3),(4,1),(4,2),(4,3),(5,3),(5,3),(5,3)
然後就開始一最重要的工作:虛虛(……)相連,作者下面的xx是Vector,結構體其實也可以,你xx就是存每條邊的資訊(p,q,c),zx處理完迴圈一遍,我們目的就是要在zx中找p所對應c屬性第一個虛點和q屬性所對應r屬性第一個虛點,然後連線,由於zx已經sort,所以不需要一遍一遍迴圈找,直接呼叫algorithm裡面的lower_bound(下界二分查詢),在zx裡面找出兩虛點編號,相連,不就可以了嗎?

關於坑點

有小盆友在處理虛點時用了一個與xx包括zx儲存資訊(這是假的!!),但加了每個虛點的編號(處理zx時乾的),然後他把xx按端點為第二關鍵字,屬性為第一關鍵字排序,然後將c屬性一樣的虛點相連(排在了一起),因為只要出現,就必定相連,就是自己重新構了個網,其實這樣也是說得通的,雖然重新連了點,但同一網下相連邊權值為0,沒問題
But
誰告訴你屬性一樣的虛點一定相連呢?
反例如下:
反例
正解是3
所以,做日語+英文題時(尤其是在AtCoder)一定要小心!謹慎!

程式碼

#include<set>  
#include<map>  
#include<stack>  
#include<cstdio>  
#include<queue>  
#include<cmath>  
#include<vector>  
#include<climits>  
#include<cstring> 
#include<iostream> 
#include<algorithm> 
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
int read(){ 
    int f=1,x=0; 
    char c=getchar(); 
    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();} 
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();} 
    return f*x; 
}
#define MAXN 2200000
struct edge{
    int v,w;
    edge(){}
    edge(int V,int W){v=V,w=W;}
};
int n,m;
struct node{
    int u,k;
    node(){}    
    node(int U,int K){u=U,k=K;}
    friend bool operator < (node a,node b){return a.u==b.u?a.k<b.k:a.u<b.u;}
};
struct node1{
    int u,v,k;
    node1(){}   
    node1(int U,int V,int K){u=U,v=V,k=K;}
};
vector<node> Make_zx;
vector<node1> Make_xx;
vector<edge> G[MAXN+5];
int dis[MAXN+5];
bool vis[MAXN+5];
queue<int>Q;
int SPFA(int S,int T){
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    dis[S]=0,vis[S]=1,Q.push(S);
    while(!Q.empty()){
        int u=Q.front(),v,w,siz=G[u].size();
        Q.pop();
        vis[u]=0;
        for(int i=0;i<siz;i++){
            v=G[u][i].v,w=G[u][i].w;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                if(!vis[v]){
                    vis[v]=1;
                    Q.push(v);
                }
            }
        }
    }
    return dis[T]==INF?-1:dis[T]/2;
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++){
        int p=read()-1,q=read()-1,c=read()-1;
        Make_zx.push_back(node(p,c));
        Make_zx.push_back(node(q,c));
        Make_xx.push_back(node1(p,q,c));
        Make_xx.push_back(node1(q,p,c));
    }
    sort(Make_zx.begin(),Make_zx.end());
    for(int i=0;i<int(Make_zx.size());i++){
        G[n+i].push_back(edge(Make_zx[i].u,1));
        G[Make_zx[i].u].push_back(edge(n+i,1));
    }
    for(int i=0;i<int(Make_xx.size());i++){
        int l1=lower_bound(Make_zx.begin(),Make_zx.end(),node(Make_xx[i].u,Make_xx[i].k))-Make_zx.begin(),
        l2=lower_bound(Make_zx.begin(),Make_zx.end(),node(Make_xx[i].v,Make_xx[i].k))-Make_zx.begin();
        G[l1+n].push_back(edge(l2+n,0));
    }
    printf("%d\n",SPFA(0,n-1));
    return 0;
}