C. Sakurako's Field Trip
https://codeforces.com/problemset/problem/2033/C
题意:给定n个数,如果i∈[1, n - 1]且a[i] == a[i + 1],则总的代价加1,现在可以任意交换a[i]和a[n - i + 1],求任意交换后的最小代价。
思路:动态规划,假想从两边向中间收缩,dp[i][0]和dp[i][1]分别表示走了i步时,不交换和交换的最小代价,可以得到转移方程:dp[i][0] = min(dp[i - 1][1] + ..., dp[i - 1][0] + ...), dp[i][1] = min(dp[i - 1][0] + ..., dp[i - 1][1] + ...)。最后,我们根据n的长度,来决定如何输出。假如n是奇数,那么直接输出最后一步的结果。假如n是偶数,那么在输出时,需要考虑最中间的两个数是否相等,因为不论如何交换,这两个数总是相邻的。
总结:这个题目有点像之前看过的一个传纸条的题目,印象里是从左上角往右下角传两个纸条,并且这两个纸条走的路径不能有重合,并且,每个格子里有个代价,问两个纸条到达右下角的最小还是最大代价。两个题目的相似点是,他们的状态有一些限制,并不能随意的走动,就是有一种互斥的感觉。 因为如果各自走各自的,那么最终将两个结果合并,我们不能保证这两个结果合在一起后,仍然是一个合法的路径,所以我们就在他们的每一步中,增加限制,保证他们不会相交。 根据这个思路,延申到当前的题目就是,我们限制了当前位置i不交换时,后面的位置一定是a[n - i + 1],而当前位置为a[n - i + 1]时,后面的位置一定是a[i],这样保证了状态的合法性,而为了保障这种状态的合法性,那么从步数作为状态去考虑,是一个理想的选择,而如果从前往后或者从后往前,一个维度单向走,好像都不太行。
代码不多,怎么感觉这题这么恶心呢。。
inline void solve() {
int n;
cin >> n;
vector<int> a(n);
for (auto& x : a) {
cin >> x;
}
vector<array<int, 2>> dp(n, {INF, INF});
dp[0][0] = dp[0][1] = 0;
for (int i = 1; i < (n + 1) / 2; ++i) {
dp[i][0] = min(dp[i - 1][0] + (a[i] == a[i - 1]) + (a[n - 1 - i] == a[n - i]),
dp[i - 1][1] + (a[i] == a[n - i]) + (a[n - 1 - i] == a[i - 1]));
dp[i][1] = min(dp[i - 1][0] + (a[n - 1 - i] == a[i - 1]) + (a[i] == a[n - i]),
dp[i - 1][1] + (a[n - 1 - i] == a[n - i]) + (a[i] == a[i - 1]));
}
if (n & 1) {
cout << min(dp[n / 2][0], dp[n / 2][1]) << '\n';
}
else {
int i = n / 2 - 1;
cout << min(dp[i][0], dp[i][1]) + (a[i] == a[i + 1]) << '\n';
}
}

浙公网安备 33010602011771号