题目链接
Codeforces 2062F Traveling Salescat
题目大意
给定一张图含 \(N\) 个点,第 \(i\) 点有属性 \(a_i\) 和 \(b_i\),任意两点间都有无向边,边权为 \(max(a_i + b_j, a_j + b_i)\) ,对于特定整数 \(k\) ,求一条恰经过 \(k\) 个不同点的路径上的边权和的最小值。
思路
看到 \(max\) 内部和多个点有关的任务,如果暴力处理求 \(max\) 肯定会多一个 \(N\) 的复杂度,第一想法就是想办法在 \(max\) 内部把 \(i\) 和 \(j\) 分离开来。
基于这个思路,很容易想到如下公式:
其中 \(w_{ij}\) 为 \(i\) 和 \(j\) 间的边权。
此时很显然的,如果我先把 \(N\) 个点按照 \(a_i - b_i\) 排序,我就可以保证:
不妨真的这么排序,然后记这条路径为 \(v_s, v_1, v_2 ... v_k-2, v_t\), 其中 \(v_s\) 和 \(v_t\) 分别是路径起终点,此时我们可以分类讨论:
- 考虑 \(v_1, v_2 ... v_k-3, v_k-2\) 构成的边,也就是中间一般点间的边,为保证整条路径的边权最小,这类节点按照排序后的顺序从小到大(或从大到小,为行文方便这里以从小到大举例)访问可以得到最优解,这一部分对于答案的贡献为:
-
考虑 \(v_s, v_1\) ,若 \(v_s < v_1\) ,那么对答案的贡献为 \(b_{v_s} + a_{v_1}\) ;否则,对答案的贡献为 \(b_{v_1} + a_{v_s}\),这会导致 \(v_1\) 对于答案的贡献为 \(2b_{v_1}\)。
-
考虑 \(v_{k-2}, v_t\) ,若 \(v_{k-2} < v_t\) ,那么对答案的贡献为 \(b_{v_{k-2}} + a_{v_t}\) ;否则,对答案的贡献为 \(b_{v_t} + a_{v_{k-2}}\),这会导致 \(v_{k-2}\) 对于答案的贡献为 \(2a_{v_{k-2}}\)。
现在,对于一条路径我们已经有了求解办法,然而枚举所有路径是不现实的,如何高效求解最小的边权和?
很显然动态规划,对于这类问题有一种朴素的构造方法,我们可以枚举点 \(i \ in [1, n]\),表示考虑前 \(i\) 个点,记 \(dp_k\) 为此时路径包含 k 个点时的最优解,但是我们发现,一般点状态好转移,涉及起终点的状态不好转移,故我们可以加一个维度,重新构造。
枚举点 \(i \in [1, n]\),表示考虑前 \(i\) 个点,记 \(dp_{k,t}\) 为此时路径包含 k 个点时的最优解,其中
- \(t=0\) 既没考虑起点也没考虑终点
- \(t=1\) 考虑起点没考虑终点
- \(t=2\) 考虑起点,考虑终点
此外理论还有一种状态既考虑终点没考虑起点的状态,但是显然考虑起终点的顺序不影响答案,故可以省略,然后编码就很轻松了,记得像背包一样,一个点一轮只能更新 \(dp\) 一次,故我用 \(tdp\) 暂存更新,整轮更新完成再覆盖。
AC代码
#include <iostream>
#include <algorithm>
using namespace std;const int N = 3005;
const long long INF = 0x3f3f3f3f3f3f3f3f;
class City{
public:long long a, b;bool operator<(const City &o) const {return a-b<o.a-o.b;}
};City city[N];long long ans[N];long long dp[N][3];
long long tdp[N][3];int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int T;cin>>T;while(T--) {int n;cin>>n;for(int i=1;i<=n;i++) {cin>>city[i].a>>city[i].b;ans[i] = INF;for(int j=0;j<3;j++) {tdp[i][j] = INF;}}sort(city+1, city+1+n);for(int i=1;i<=n;i++) {for(int j=0;j<=n;j++) {for(int k=0;k<3;k++) {dp[j][k] = tdp[j][k];}}tdp[1][0] = min(tdp[1][0], city[i].b*2); // vs > v1 时,v1的贡献tdp[1][1] = min(tdp[1][1], city[i].b); // vs < v1 时,vs的贡献for(int j=1;j<=n;j++) {for(int k=0;k<3;k++) {tdp[j+1][k] = min(tdp[j+1][k], dp[j][k] + city[i].a + city[i].b); // 一般点的贡献}tdp[j+1][1] = min(tdp[j+1][1], dp[j][0] + city[i].a); // vs > v1 时,vs的贡献tdp[j+1][2] = min(tdp[j+1][2], dp[j][1] + city[i].b); // vt < v_k-2 时,v_t的贡献ans[j+1] = min(ans[j+1], dp[j][1] + city[i].a); // vt > v_k-2 时,vt的贡献ans[j+1] = min(ans[j+1], dp[j][2] + city[i].a*2); // vt < v_k-2 时,v_k-2的贡献}}for(int i=2;i<=n;i++) {cout<<ans[i]<<" ";}cout<<endl;}
}