- 前言
- 题目
- 题目链接
- 题目大意
- 数据范围
- 思路
- 建图方法
- 关于实现
- 关于坑点
- 代码
前言
做了一下午的真心奉献
题目
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:
三就不打了
你一定要把它想象一下(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;
}