P9402 [POI2020-2021R3] Droga do domu

发布时间 2023-09-01 21:25:24作者: Thunder_S

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\)

这样的话,如果我们顺序枚举 \(k\),就可以做到无后效性,从而 \(dp\)

\(f_{i,j}\) 表示到了节点 \(i\),此时换乘了 \(k\) 次的最小时间。注意这里的 \(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;
}