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

前言

做了一下午的真心奉献

题目

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;
}

猜你喜欢

转载自blog.csdn.net/qq_37555704/article/details/81103930
今日推荐