1 条题解
-
0
一、题意理解
我们有一个有向图,,, 个任务。
每个任务 表示:
- 在 领取货物:时间
- 在 交付货物:时间
- 领货和交货不耗时,移动耗时(边权)
起点:时刻 在点 。
目标:选择一些任务并安排顺序,使得能完成的任务数最多。
二、样例分析
样例图:
1 -> 2 (1) 2 -> 3 (1) 3 -> 4 (1) 4 -> 5 (1)任务:
- (1,2,3,4)
- (2,3,1,2)
- (3,4,3,4)
解释:
- 时刻0在1
- 时刻1到2(走1->2,耗时1)
- 此时任务2可领货(,当前时刻1),领货后立即去3(2->3,耗时1),时刻2到达3,交货(,刚好)。
- 在3逗留到时刻3(任务3 ),领货后去4(3->4,耗时1),时刻4到达4,交货(,刚好)。
完成任务2和3,共2个。
三、关键点
- 任务数 很小(),可以考虑状态压缩 DP 或枚举任务顺序。
- 我们需要知道任意两点之间的最短路径时间(Floyd 预处理)。
- 一个任务能否完成,取决于到达 的时间 且到达 的时间 。
- 任务顺序可以任意,但必须满足时间约束。
四、预处理最短路径
由于 ,可以用 Floyd-Warshall 算法计算所有点对之间的最短时间 。
复杂度 可行。
五、状态设计
设 表示已经完成的任务集合为 (二进制位表示),当前在点 的最早可能时间。
- 大小
- 大小
- 总状态数约
六、状态转移
从 出发:
-
尝试接新任务 (不在 中):
- 从 到 需要时间
- 到达 的时间
- 必须在 才能领货(如果 ,可以等到 再领,所以实际领货时间 )
- 从 到 需要
- 到达 的时间
- 必须 才能完成该任务
- 如果可以完成,则: $ dp[mask \mid (1<<i)][t_i] = \min( dp[mask \mid (1<<i)][t_i],\ T_2 ) $
-
也可以不接任务,直接走到其他点 (不接任务单纯移动): $ dp[mask][v] = \min( dp[mask][v],\ dp[mask][u] + dist[u][v] ) $ 但这一步其实可以省略,因为接任务时已经隐含了移动。
实际上,我们只需枚举下一个要接的任务,因为单纯移动可以在接任务时体现(先移动到任务起点)。
七、算法步骤
- 读入 ,建图。
- Floyd 计算 (注意初始化为无穷大,)。
- 初始化 ,其余 。
- 对 到 :
- 对每个 ,如果 :
- 对每个未完成的任务 :
- 如果 :
- $dp[mask | (1<<i)][t_i] = \min(dp[mask | (1<<i)][t_i], T_2)$
- 对每个未完成的任务 :
- 对每个 ,如果 :
- 答案 = 满足 的 中二进制位 1 的个数的最大值。
八、复杂度
- 状态数:
- 转移:每个状态尝试 个任务 → ,可接受。
九、代码实现(C++)
#include <bits/stdc++.h> #define int long long #define FAST ios::sync_with_stdio(false);cin.tie(0) using namespace std; int n, m, q; int dis[40][40]; vector<pair<int, int>> v[40]; //v[i] 存储了在第 i 个点发生的事件 //{time,op} 时间,事件类型(接收,领取) int f[40][59049]; int mi[40]; /* f[i][state]当前在 i 城市,state 表示每个订单的状态, 0 表示没有领取 1 表示在车上 2 表示已经完成 所需的最小时间 枚举接下来去哪个城市?然后更新状态 可以逗留一段时间领取更多的包裹 */ int change(int x, int op) { //状态,经过了 op 的洗礼 int F = abs(op) - 1; int K = x % mi[F]; x /= mi[F]; if (x % 3 == 0 && op > 0) x++; else if (x % 3 == 1 && op < 0) x++; x *= mi[F]; x += K; return x; } bool cmp(pair<int, int> X, pair<int, int> Y) { if (X.first == Y.first) return X.second > Y.second; return X.first < Y.first; } signed main() { cin >> n >> m >> q; memset(dis, 0x3f, sizeof(dis)); for (int i = 1; i <= n; i++) dis[i][i] = 0; for (int i = 1; i <= m; i++) { int x, y, c; cin >> x >> y >> c; dis[x][y] = min(dis[x][y], c); } for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]); } } } // for (int i=1;i<=n;i++){ // for (int j=1;j<=n;j++){ // cout << dis[i][j] << ' '; // } // cout << "\n"; // } for (int i = 1; i <= q; i++) { int s, t, l, r; cin >> s >> t >> l >> r; v[s].push_back({l, i}); v[t].push_back({r, -i}); } mi[0] = 1; for (int i = 1; i <= q; i++) mi[i] = mi[i - 1] * 3; for (int i = 1; i <= n; i++) { sort(v[i].begin(), v[i].end(), cmp); } memset(f, 0x3f, sizeof(f)); //f[i] 只要不是 inf 就可以贡献到答案 for (int i = 1; i <= n; i++) f[i][0] = dis[1][i]; int ans = 0; for (int st = 0; st < mi[q]; st++) { for (int cur = 1; cur <= n; cur++) { //必须逗留到状态改变了才能去其它城市 int to = st; int MX = f[cur][st]; for (auto j : v[cur]) { if (j.second > 0) { to = change(to, j.second); MX = max(MX, j.first); } else { if (j.first >= f[cur][st]) to = change(to, j.second); } if (to != st) { //改变咯! for (int k = 1; k <= n; k++) f[k][to] = min(f[k][to], MX + dis[cur][k]); } } if (f[cur][st] < 1e18) { int cnt = 0, C = st; while (C) { if (C % 3 == 2) ++cnt; C /= 3; } ans = max(ans, cnt); } } } cout << ans << "\n"; return 0; }
十、总结
本题的关键在于:
- 利用 小的特点进行状态压缩 DP。
- 预处理任意两点最短路径。
- 状态设计为 表示已完成任务集合和当前位置。
- 转移时检查时间约束:领货时间 ,交货时间 。
- 1
信息
- ID
- 4912
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者