题解:AtCoder AT_awc0098_d City Tour Rally
【题目来源】
AtCoder:D - City Tour Rally
【题目描述】
Takahashi is participating in a rally that goes through \(N\) cities. The cities are numbered \(1\) through \(N\).
This rally takes place over \(K\) days, from Day 1 to Day \(K\). Takahashi stays in exactly one city each day. On Day 1, he can choose and stay in any of the \(N\) cities. From Day 2 onwards, he moves from the city he stayed in on the previous day to another city using a travel route, and stays there.
There are \(M\) travel routes available between cities, all of which are one-way. The \(k\)-th route represents travel from city \(a_k\) to city \(b_k\). Using this route allows him to stay in city \(b_k\) on the day after he stayed in city \(a_k\) (travel in the reverse direction is not necessarily possible). It is guaranteed that \(a_k \neq b_k\) for each route.
From Day 2 onwards, he can only move to a city if there is a travel route starting from the city he stayed in on the previous day. Since there are no self-loops to the same city, he cannot stay in the same city for two consecutive days. However, he is allowed to stay in the same city again with other cities in between.
Each city \(i\) has a base score \(P_i\). The score obtained by staying in city \(i\) on Day \(j\) (\(1 \leq j \leq K\)) is \((P_i \times j) \bmod Q\). Here, \(Q\) is a given positive integer, and \(\bmod\) denotes the non-negative remainder.
A staying plan for the \(K\) days is a sequence of city numbers of length \(K\), \(c_1, c_2, \ldots, c_K\), that satisfies all of the following conditions:
- Each \(c_j\) (\(1 \leq j \leq K\)) is an integer between \(1\) and \(N\), inclusive (the same city number may appear multiple times in the sequence).
- For all \(1 \leq j \leq K - 1\), there exists a travel route from city \(c_j\) to city \(c_{j+1}\).
Please maximize the total score over the \(K\) days, which is:
\(\sum_{j=1}^{K} \bigl((P_{c_j} \times j) \bmod Q\bigr)\)
Find the maximum value of this sum over all valid staying plans.
It is guaranteed that at least one valid staying plan exists.
高橋正在参加一场穿越 \(N\) 个城市的拉力赛。城市编号为 \(1\) 到 \(N\)。
这场拉力赛持续 \(K\) 天,从第 \(1\) 天到第 \(K\) 天。高橋每天恰好待在一个城市。第 \(1\) 天,他可以选择并待在任意一个 \(N\) 个城市中。从第 \(2\) 天开始,他从前一天所在的城市通过一条旅行路线移动到另一个城市,并在那里停留。
城市之间有 \(M\) 条单向旅行路线。第 \(k\) 条路线表示从城市 \(a_k\) 到城市 \(b_k\) 的旅行。使用这条路线可以让他在待过城市 \(a_k\) 的第二天待在城市 \(b_k\)(反向旅行不一定可行)。保证每条路线都满足 \(a_k \neq b_k\)。
从第 \(2\) 天开始,他只能移动到前一天所在城市有出发旅行路线的城市。由于没有指向同一城市的自环路线,他不能连续两天待在同一城市。但是,中间经过其他城市后,他可以再次回到同一城市。
每个城市 \(i\) 有一个基础分数 \(P_i\)。在第 \(j\) 天(\(1 \leq j \leq K\))待在城市 \(i\) 获得的分数为 \((P_i \times j) \bmod Q\)。这里 \(Q\) 是给定的正整数,\(\bmod\) 表示非负余数。
\(K\) 天的停留计划是一个长度为 \(K\) 的城市编号序列 \(c_1, c_2, \ldots, c_K\),满足以下所有条件:
- 每个 \(c_j\)(\(1 \leq j \leq K\))是 \(1\) 到 \(N\) 之间的整数(序列中同一城市编号可以出现多次)。
- 对于所有 \(1 \leq j \leq K - 1\),存在从城市 \(c_j\) 到城市 \(c_{j+1}\) 的旅行路线。
请最大化 \(K\) 天的总分数:
求所有有效停留计划中该总和的最大值。
保证至少存在一个有效的停留计划。
【输入】
\(N\) \(M\) \(K\) \(Q\)
\(P_1\) \(P_2\) \(\ldots\) \(P_N\)
\(a_1\) \(b_1\)
\(a_2\) \(b_2\)
\(\vdots\)
\(a_M\) \(b_M\)
- The first line contains the number of cities \(N\), the number of travel routes \(M\), the number of days of the rally \(K\), and the positive integer \(Q\) used for score calculation, separated by spaces.
- The second line contains the base scores of the cities \(P_1, P_2, \ldots, P_N\), separated by spaces.
- The \(k\)-th of the following \(M\) lines (\(1 \leq k \leq M\)) contains \(a_k\) and \(b_k\) separated by a space, representing that there is a one-way travel route from city \(a_k\) to city \(b_k\). If \(M = 0\), this section is empty.
【输出】
Print the maximum total score over the \(K\) days in a single line.
【输入样例】
3 3 4 10
2 5 7
1 2
2 3
3 1
【输出样例】
24
【核心思想】
-
问题分析:给定 \(N\) 个城市构成的有向图(\(M\) 条单向边),需要规划一条长度为 \(K\) 天的行走序列 \(c_1, c_2, \ldots, c_K\),满足相邻两天之间有旅行路线。每天在城市 \(i\) 获得分数 \((P_i \times j) \bmod Q\)(\(j\) 为天数)。目标是最大化 \(K\) 天总分数。这是一个线性动态规划问题,关键在于按天数顺序逐层递推,利用图的结构约束进行状态转移。
-
算法选择:
- 线性 DP(按天递推):将天数 \(i\) 作为 DP 的阶段,城市 \(j\) 作为状态维度
- 反向建图优化:将原图的边反向存储,使 DP 转移时能 \(O(1)\) 获取所有前驱节点,避免遍历全图
-
关键步骤:
- 初始化:读取 \(N, M, K, Q\),城市基础分数 \(P[1..N]\),以及 \(M\) 条有向边
- 反向建图:对于每条边 \((u, v)\),将 \(u\) 加入 \(g[v]\)(\(g[v]\) 存储所有能到达 \(v\) 的前驱城市)
- DP 初始化(第 \(1\) 天):
- 遍历所有城市 \(j\) 从 \(1\) 到 \(N\):
dp[1][j] = (P_j \times 1) \bmod Q:第 \(1\) 天可以在任意城市开始
- 遍历所有城市 \(j\) 从 \(1\) 到 \(N\):
- DP 转移(第 \(2\) 天到第 \(K\) 天):
- 枚举天数 \(i\) 从 \(2\) 到 \(K\),枚举当天城市 \(j\) 从 \(1\) 到 \(N\)
- 计算当天得分:
score = (P_j \times i) \bmod Q - 枚举 \(g[j]\) 中所有前驱城市 \(t\):
dp[i][j] = \max(dp[i][j], dp[i-1][t] + score):从可达的前驱城市转移,取最大值
- 统计答案:
- 遍历所有城市 \(j\) 从 \(1\) 到 \(N\):
ans = \max(ans, dp[K][j]):第 \(K\) 天在任意城市的最大总分数
- 遍历所有城市 \(j\) 从 \(1\) 到 \(N\):
-
时间/空间复杂度:
- 时间复杂度:\(O(K \times (N + M))\),\(K\) 天每层遍历 \(N\) 个城市,每个城市遍历其前驱节点(总边数 \(M\))
- 空间复杂度:\(O(N \times K + N + M)\),DP 数组 \(dp[K][N]\),反向图 \(g[N]\) 存储 \(M\) 条边
-
线性 DP 的核心思想:
- 阶段划分:将天数 \(i\) 作为天然的 DP 阶段,问题具有明显的时间顺序性,无法跳跃或回溯
- 状态设计:
dp[i][j]表示第 \(i\) 天在城市 \(j\) 时的最大累计分数,状态完全由前一天的状态推导 - 图约束融入转移:通过反向建图,将"是否存在从 \(t\) 到 \(j\) 的边"这一约束转化为"只从 \(j\) 的前驱节点转移",避免无效状态
- 取模得分的处理:每天的得分 \((P_j \times i) \bmod Q\) 只与当天有关,独立计算后叠加,不影响 DP 结构
- 适用场景:具有明确时间/阶段顺序的图上的路径优化问题,每阶段决策仅依赖前一阶段状态
【算法标签】
线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 将 int 定义为 long long,防止数值溢出
const int N = 1005; // 最大城市数量
int n, m, k, mod; // n: 城市数, m: 路线数, k: 天数, mod: 取模数Q
int p[N]; // p[i]: 城市i的基础分数
int dp[N][N]; // dp[i][j]: 第i天待在城市j的最大总分数
vector<int> g[N]; // g[j]: 能到达城市j的所有前驱城市(反向建图)
signed main() // 使用 signed 替代 int,因为 #define int long long
{
cin >> n >> m >> k >> mod; // 读入城市数、路线数、天数、模数
// 读入每个城市的基础分数
for (int i = 1; i <= n; i++)
cin >> p[i];
// 读入M条单向路线,并反向建图
// g[v]存储所有能到达v的城市u,方便后续DP时查找前驱
while (m--)
{
int u, v;
cin >> u >> v;
g[v].push_back(u);
}
// 初始化DP数组为负无穷(表示不可达)
memset(dp, -0x3f, sizeof(dp));
// 第1天:可以在任意城市开始
for (int j = 1; j <= n; j++)
dp[1][j] = (p[j] * 1) % mod; // 第1天在城市j的分数
// 第2天到第K天:DP转移
for (int i = 2; i <= k; i++)
{
for (int j = 1; j <= n; j++) // 枚举第i天所在城市j
{
int score = (p[j] * i) % mod; // 第i天在城市j获得的分数
// 枚举所有能到达城市j的前驱城市t
for (int t : g[j])
{
// 状态转移:从第i-1天在城市t转移过来
dp[i][j] = max(dp[i][j], dp[i - 1][t] + score);
}
}
}
// 答案:第K天在任意城市的最大总分数
int ans = -1e18;
for (int j = 1; j <= n; j++)
ans = max(ans, dp[k][j]);
cout << ans << endl; // 输出最大总分数
return 0;
}
【运行结果】
3 3 4 10
2 5 7
1 2
2 3
3 1
24
浙公网安备 33010602011771号