【JSOI2015】地铁线路
Cry_For_theMoon · · 题解
传送门
图论建模优秀题。解法其实很巧妙,但是切掉一道题后只有深入思考是如何想到这个做法的,这道题目才是真正有意义的
考虑处理线路,同一线路上的所有点花费1代价相互可达,我们不可能对每个点去和与他同一线路上的点连边,因为
对于第一问,显然就是最短路。考虑
此时分两种情况,转移点在当前点之前/之后,我们只分析之前(之后的分析是一样的),设当前点是
在一开始的建图中,我和 M_sea 神仙的方法是一样的。事实上根据我们刚才的分析,完全可以改成“所有点去练虚点的边权为0,虚点连所有点的边权为1”(即反一下),然后DP的时候其实是转移铁路上所有最短路=虚点最短路+1的点,而上文中
虽然这题DP是重头戏但是这题DP做法我觉得带给我们的帮助其实并不是很大,反而是建图方法值得好好揣摩。
但是我知道你们都只会看Code的
//JSOI,2015
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<string>
using namespace std;
const int MAXN=4e5+10,MAXM=2e6+10,INF=1e9;
struct Edge{
int u,v,w;
};
struct Node{
int u,dis;
bool operator<(const Node& n2)const{
return dis > n2.dis;
}
};
struct Line{
int u,d;
bool operator<(const Line& n2)const{
return d < n2.d;
}
}Lines[MAXN];
map<string,int>fs;
Edge edge[MAXM];
int first[MAXN],next[MAXM],tot;
int dis[MAXN],vis[MAXN],f[MAXN],point[MAXN];
int lines,n;
inline void addedge(int u,int v,int w){
edge[++tot].u=u;edge[tot].v=v;edge[tot].w=w;
next[tot]=first[u];first[u]=tot;
}
inline int getstation(int u){
return u+n;
}
void dijkstra(int s){
for(int i=1;i<=lines+n;i++){
dis[i] = INF;
}
dis[s] = 0;
priority_queue<Node>h;h.push((Node){s,0});
while(!h.empty()){
Node now = h.top();h.pop();
int u = now.u;
if(vis[u])continue;
vis[u] = 1;
for(int j=first[u];j;j=next[j]){
int v = edge[j].v;
if(dis[v] > dis[u]+edge[j].w){
dis[v] = dis[u]+edge[j].w;
h.push((Node){v,dis[v]});
}
}
}
}
int main(){
scanf("%d%d",&lines,&n);
string tmpname;
for(int i=1;i<=n;i++){
cin>>tmpname;fs[tmpname]=i;
}
for(int i=1,l;i<=lines;i++){
scanf("%d",&l);
for(int j=1;j<=l;j++){
cin>>tmpname;
int u = getstation(i),v = fs[tmpname];
addedge(v,u,0);//上车
addedge(u,v,1);//下车
}
}
string startname,endname;
cin>>startname>>endname;
int s = fs[startname],e = fs[endname];
dijkstra(s);
if(dis[e]==INF){
printf("-1\n0");return 0;
}
printf("%d\n",dis[e]);
for(int i=1;i<=lines;i++){
Lines[i] = (Line){getstation(i),dis[getstation(i)]};
}
sort(Lines+1,Lines+1+lines);
for(int i=1;i<=n;i++)f[i]=-INF;
f[s]=0;
for(int i=1;i<=lines;i++){
int u = Lines[i].u;
if(dis[u]==INF)break;
int rear = 0;
for(int j=first[u];j;j=next[j]){
int v = edge[j].v;
point[++rear] = v;
}
int maxn = 0;
for(int j=1;j<=rear;j++){
int v = point[j];
if(dis[v]==dis[u]+1){
if(maxn!=0){
f[v] = max(f[v],f[point[maxn]]+j-maxn);
}
}else if(dis[v]==dis[u]){
if(maxn){
if(f[point[maxn]]-maxn < f[point[j]]-j)maxn=j;
}else{
maxn=j;
}
}
}
maxn = 0;
//转移前缀
for(int j=rear;j>=1;j--){
int v = point[j];
if(dis[v]==dis[u]+1){
if(maxn!=0){
f[v] = max(f[v],f[point[maxn]]+maxn-j);
}
}else if(dis[v]==dis[u]){
if(maxn){
if(f[point[maxn]]+maxn < f[point[j]]+j)maxn=j;
}else{
maxn=j;
}
}
}
}
printf("%d",f[e]);
return 0;
}