P9402 [POI2020-2021R3] Droga do domu
Description
\(n\) 个点, \(m\) 条边,无重边自环,边有长度。
1 号点是学校, \(n\) 号点是家。
\(s\) 条公交线路。公交逢点必停,且一个点不会停两次。在一条边上行驶的时间就是它的长度。给定了第一班公交发车时间和发车间隔。
在时刻 \(t\) 从学校出发,至多换乘 \(k\) 次,求最早什么时候到家。
只计算路上时间和等车时间。换乘时间不计。
\(2\le n\le 10000,1\le m\le 50000,1\le s\le 25000,0\le k\le 100,0\le t\le 10^9,\sum l\le 50000\)。
Solution
一个图,求起点到终点的最小时间
但是怎么建图。如果直接在原图上面跑,是否换乘了这个问题将难以解决。但是注意到 \(\sum l\le 50000\)。这意味着我们可以对于每条公交线,建 \(\sum l\) 个点,拉成 \(s\) 条链。那么在这个链上转移就不需要换乘,从一条链到另一条链就换乘次数增加 \(1\)。
这样的话,如果我们顺序枚举 \(j(1\sim k)\),就可以做到无后效性,从而 \(dp\)。
设 \(f_{i,j}\) 表示到了节点 \(i\),此时换乘了 \(j\) 次的最小时间。注意这里的 \(i\) 的范围是 \(\sum l\)。
那么转移就可以从本链上的点不换乘转移,也可以从别的链上转移过来。
因为是链,所以每个点只会转移到一个点,而且链上节点的顺序是递增的。因此直接枚举 \(i\),用 \(f_{i,j}\) 去更新 \(f_{to_i,j}\)。
至于从别的链上转移,我们可以记录原图中节点 \(i\) 在新图中对应的节点是什么。在这些节点中找到最小的 \(f_{i,j-1}\),去更新 \(f_{i,j}\)。即使 \(f_{x,j}\) 被 \(f_{x,j-1}\) 更新也没有关系,因为换乘次数少的不会劣。
注意更新的顺序。要先更新了换乘的,再去更新不换乘的。这与坐车的顺序是一样的,先选择线路,再做到下一站。
时间复杂度 \(\mathcal O(k\sum l)\)。
Code
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define N 10005
#define M 50005
#define K 105
#define ll long long
using namespace std;
int n,m,s,k,cnt,rt[M];
ll t,inf,ans,f[M][K];
struct edg {int to;ll val;};
struct node
{
int to;
ll len;
ll st,ti;
}a[M];
struct que{int x,k;ll t;bool bj;};
vector<edg> g[N];
vector<int> p[N];
queue<que> q;
ll read()
{
ll res=0;char ch=getchar();
while (ch<'0'||ch>'9') ch=getchar();
while (ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch-'0'),ch=getchar();
return res;
}
void add(int x,int y,ll z,ll st,ll ti) {a[x].to=y;a[x].len=z;a[x].st=st;a[x].ti=ti;}
bool cmp(edg x,edg y) {return x.to<y.to;}
ll calc(ll nowt,ll st,ll ti)
{
if (nowt<=st) return st;
ll tn=(nowt-st)/ti;
if ((nowt-st)%ti!=0) tn++;
return tn*ti+st;
}
int main()
{
n=read();m=read();s=read();k=read();t=read();
for (int i=1;i<=m;++i)
{
int x,y;ll z;
x=read();y=read();z=read();
g[x].push_back((edg){y,z});
g[y].push_back((edg){x,z});
}
for (int i=1;i<=n;++i)
sort(g[i].begin(),g[i].end(),cmp);
for (int i=1;i<=s;++i)
{
int num=read();ll x=read(),y=read();
int lst=0,u=0;
for (int j=1;j<=num;++j)
{
int v;v=read();
++cnt;
p[v].push_back(cnt);rt[cnt]=v;
if (!u) u=v,lst=cnt;
else
{
int l=0,r=g[u].size()-1,mid,res=0;
while (l<=r)
{
mid=(l+r)>>1;
if (v<=g[u][mid].to) res=mid,r=mid-1;
else l=mid+1;
}
add(lst,cnt,g[u][res].val,x,y);
lst=cnt;x+=g[u][res].val;u=v;//找到链上每条边的权值
}
}
}
memset(f,127,sizeof(f));
inf=f[0][0];
for (int i=0;i<p[1].size();++i)
f[p[1][i]][0]=t;
for (int x=1;x<=cnt;++x)
{
int y=a[x].to;
if (!y) continue;
ll nxt=calc(f[x][0],a[x].st,a[x].ti)+a[x].len;
f[y][0]=min(f[y][0],nxt);
}
for (int j=1;j<=k;++j)
{
for (int now=1;now<=n;++now)
{
ll mn=inf;
for (int i=0;i<p[now].size();++i)
mn=min(mn,f[p[now][i]][j-1]);
for (int i=0;i<p[now].size();++i)
f[p[now][i]][j]=mn;
}//先换乘
for (int x=1;x<=cnt;++x)
{
int y=a[x].to;
if (!y) continue;
ll nxt=calc(f[x][j],a[x].st,a[x].ti)+a[x].len;
f[y][j]=min(f[y][j],nxt);
}//再坐到下一站
}
ans=inf;
for (int i=0;i<p[n].size();++i)
for (int j=0;j<=k;++j)
ans=min(ans,f[p[n][i]][j]);
if (ans>=inf) printf("NIE\n");
else printf("%lld\n",ans);
return 0;
}