首先可以发现,本题实际上类似一个 DAG 上的背包问题。
那么我们有一个很显然的做法:
设 f[v][t]f[v][t]f[v][t] 表示从点 111 到达点 vvv,路径长度为 ttt 的所有方案中的最大点数,转移方程平凡,这里略去。
但是 T≤109T \leq 10^9T≤109 ,这样做空间会炸飞。
可喜的是,答案一定是小于等于 nnn 的,同时 n≤5000n \leq 5000n≤5000,这启发我们可以考虑 O(n2)O(n^2)O(n2) 空间的算法。
我们换个角度思考:既然枚举距离求点的最大值不行,那么枚举点数求距离的最小值是否可行?
观察数据范围,在复杂度上这显然是可行的。
所以我们试着考虑转移方程:
设 f[v][t]f[v][t]f[v][t] 表示从点 111 到达点 vvv,经过点数为 ttt 的所有方案中的最短路径。
那么我们有转移方程:(其中 prefix(v)prefix(v)prefix(v) 表示点 vvv 的前驱,即所有能够到达点 vvv 的点,www 表示边 u→vu\to vu→v 的长度)
f[v][t]=minu∈prefix(v)(f[u][t−1]+w)[f[u][t−1]+w<=T] f[v][t] = \min_{u \in prefix(v)} (f[u][t - 1] + w)[f[u][t - 1] + w <= T] f[v][t]=u∈prefix(v)min(f[u][t−1]+w)[f[u][t−1]+w<=T]
为什么是最短路径呢?因为我们并不关心路径的长度到底是多少,只关心它是否不超过 TTT。从贪心的角度考虑,这里选择最短路径显然最优。
转移的同时,我们记录一个 g[v][t]g[v][t]g[v][t],表示 f[v][t]f[v][t]f[v][t] 是由哪个 uuu 转移过来的,方便最后输出答案。
代码:(别急着抄)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
#include<cstring>
#include<vector>
#include<queue>
#define MAXN 5005
using namespace std;
int n, m, k, in[MAXN], f[MAXN][MAXN], g[MAXN][MAXN];
vector<pair<int, int> > vec[MAXN];
inline void Toposort()
{
queue<int> q;
q.push(1), f[1][1] = 0, g[1][1] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(register vector<pair<int, int> > :: iterator it = vec[u].begin(); it != vec[u].end(); it++)
{
int v = it -> first, w = it -> second;
for(register int i = 1; i <= n; i++)
{
if(g[u][i - 1] && f[u][i - 1] + w <= k && f[u][i - 1] + w < f[v][i])
{
f[v][i] = f[u][i - 1] + w;
g[v][i] = u;
}
}
--in[v]; if(!in[v]) q.push(v);
}
}
return;
}
inline void Output(int u, int k)
{
if(u > 1) Output(g[u][k], k - 1);
printf("%d ", u);
return;
}
int main()
{
#ifdef FILE
freopen("Input.in", "r", stdin);
freopen("Output.out", "w", stdout);
#endif
scanf("%d%d%d", &n, &m, &k);
for(register int i = 1; i <= m; i++)
{
int u, v, w; scanf("%d%d%d", &u, &v, &w);
vec[u].push_back(make_pair(v, w));
++in[v];
}
memset(f, 0x3F, sizeof f);
Toposort();
int t;
for(t = n; t > 1; t--) if(g[n][t]) break;
printf("%d\n", t);
Output(n, t);
return 0;
}
但是上面的代码是过不了的,Hack 数据:
4 4 10
2 1 1
2 3 1
1 3 1
3 4 1
这组数据的特点是什么?DAG 的源点不是点 111 !
虽然点 111 的所有直接前驱和间接前驱都不会影响到答案,但是它们会影响拓扑排序的过程。
比如上面的边 2→32 \to 32→3,虽然点 222 在拓扑排序中没有访问到,但是这条边对点 333 的入度仍然存在 111 的影响。
所以我们需要先 DFS 一遍,把点 111 的所有直接前驱和间接前驱对于拓扑图中点的入度影响去掉。
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
#include<cstring>
#include<vector>
#include<queue>
#define MAXN 5005
using namespace std;
int n, m, k, in[MAXN], f[MAXN][MAXN], g[MAXN][MAXN];
bool vis[MAXN];
vector<pair<int, int> > vec[MAXN];
inline void DFS(int u)
{
vis[u] = true;
for(register vector<pair<int, int> > :: iterator it = vec[u].begin(); it != vec[u].end(); it++) if(!vis[it -> first]) DFS(it -> first);
return;
}
inline void Toposort()
{
queue<int> q;
q.push(1), f[1][1] = 0, g[1][1] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(register vector<pair<int, int> > :: iterator it = vec[u].begin(); it != vec[u].end(); it++)
{
int v = it -> first, w = it -> second;
for(register int i = 1; i <= n; i++)
{
if(g[u][i - 1] && f[u][i - 1] + w <= k && f[u][i - 1] + w < f[v][i])
{
f[v][i] = f[u][i - 1] + w;
g[v][i] = u;
}
}
--in[v]; if(!in[v]) q.push(v);
}
}
return;
}
inline void Output(int u, int k)
{
if(u > 1) Output(g[u][k], k - 1);
printf("%d ", u);
return;
}
int main()
{
#ifdef FILE
freopen("Input.in", "r", stdin);
freopen("Output.out", "w", stdout);
#endif
scanf("%d%d%d", &n, &m, &k);
for(register int i = 1; i <= m; i++)
{
int u, v, w; scanf("%d%d%d", &u, &v, &w);
vec[u].push_back(make_pair(v, w));
++in[v];
}
memset(f, 0x3F, sizeof f);
DFS(1);
for(register int i = 1; i <= n; i++) if(!vis[i]) for(register vector<pair<int, int> > :: iterator it = vec[i].begin(); it != vec[i].end(); it++) --in[it -> first];
Toposort();
int t;
for(t = n; t > 1; t--) if(g[n][t]) break;
printf("%d\n", t);
Output(n, t);
return 0;
}
896



被折叠的 条评论
为什么被折叠?



