Shift + Esc
以下内容从 开始标号。
一股浓烈的 ABC 味扑面而来。类似最小化 的形式已经在 ABC 的 F 题中欧出现好多次了。
我们不关心最终答案里具体进行了多少次旋转操作,状态仅需设置 表示当前位于第 行第 列的最小代价。
我们以行为单位进行转移。对于单独的一行,我们需要考虑进行多少次旋转操作。因此,可以设 表示当前位于第 行第 列,第 行进行了 次旋转操作。
转移时,我们可以选择从上方过来,也可以选择从左侧过来。对第 列进行 次旋转后,格子 上的数变成了 ,同时会导致代价增加 。于是我们有转移方程:
完成一整行后,我们接着更新该行的 。显然每列的答案为所有位移方案中该列的最小者。
时间复杂度 。
#include <cstddef>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>
using namespace std;
istream& fin = cin;
ostream& fout = cout;
using ui = unsigned int;
using uli = unsigned long long int;
using li = long long int;
int main(void) {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
size_t T;
fin >> T;
while (T--) {
size_t n, m;
ui k;
fin >> n >> m >> k;
vector<uli> f(m, numeric_limits<uli>::max() / 2);
f[0] = 0;
for (size_t c = 0; c < n; ++c) {
vector<uli> a(m);
for (uli& i : a) fin >> i;
vector g(m, vector<uli>(m));
for (size_t t = 0; t < m; ++t) {
g[t][0] = f[0] + (uli)k * t + a[(t + 0) % m];
for (size_t i = 1; i < m; ++i)
g[t][i] = min(g[t][i - 1], f[i] + (uli)k * t) + a[(t + i) % m];
}
f = vector<uli>(m, numeric_limits<uli>::max() / 2);
for (auto const& i : g)
for (size_t j = 0; j < m; ++j) f[j] = min(f[j], i[j]);
}
fout << f.back() << '\n';
}
return 0;
}