Moortal Cowmbat
Posted zjnu-huyh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Moortal Cowmbat相关的知识,希望对你有一定的参考价值。
2020-07-06 个人赛1 H:Moortal Cowmbat
题意:
样例:
题解:
①题目文本中已经提示说,i->j直接变化的代价不一定是最小的,所以可以借助中间点,先跑一个Floyd,算出两点之间真正的最小代价备用。
②设置数组dp[i][j]表示在第i个字母为j时,前i个字母整体的最小代价。
③设置数组w[i][j]表示前i位都变成字母j的总代价,这样方便利用前缀和优化。
dp分析:
①首先,如果总个数少于2k-1,那只有一种可能,便是所有字母都一样。
②如果多于i>2k-1,当第i位为j时,dp[i][j]=min(dp[i-1][j]+改变成j的代价,min(dp[i - k][字母]+ w[i][j] - w[i - k][j]))。
含义:第i个字母为j时,前i个字母整体的最小代价=min(第i-1个字母为j第i个字母也为改变为j的整体代价,(前i-k这段以"各种字母"结尾代价的最小值)+(从第(i-k+1)位到第i位字母全部变成j的代价))。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 10;
int n, m, k;
string s;
int Map[30][30];///Map[i][j]表示字母i变成字母j的代价
int w[maxn][30];
///w[i][j]表示前i个都变成字母j的代价
///w[i][j] = w[i - 1][j] + Map[s[i - 1] - ‘a‘ + 1][j]
int dp[maxn][30];
///dp[i][j]表示在前i个中,第i个位置为字母j的最小代价
///此时要保证字符串是k连击 所以只存在以下两种情况
///1 i-1为字母j
///2 i-k+1 到 i 全都是字母j
int main()
{
cin >> n >> m >> k;
cin >> s;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++)
cin >> Map[i][j];
for (int k = 1; k <= m; k++)
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++)
Map[i][j] = min(Map[i][j], Map[i][k] + Map[k][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
w[i][j] = w[i - 1][j] + Map[s[i - 1] - ‘a‘ + 1][j];
for (int i = 1; i <= min(n, 2 * k - 1); i++)///长度小于2*k-1 只能是同一个字母
for (int j = 1; j <= m; j++)
dp[i][j] = w[i][j];
for (int i = 2 * k; i <= n; i++)
for (int j = 1; j <= m; j++)
{
///直接和上一位相同
dp[i][j] = dp[i - 1][j] + Map[s[i - 1] - ‘a‘ + 1][j];
for (int c = 1; c <= m; c++)
dp[i][j] = min(dp[i][j], dp[i - k][c] + w[i][j] - w[i - k][j]);///加上i-k+1到i都是j的代价
}
int ans = 1e9 + 7;
for (int i = 1; i <= m; i++)
ans = min(ans, dp[n][i]);
cout << ans << endl;
return 0;
}
以上代码时间复杂度O(n*m*m),完全可以过ac。
但其实还可以进一步优化:
因为我们发现,我们在最后在i,j的双重for循环中,还有一个遍历前i-k这段以"各种字母"结尾代价的最小值的循环。这个循环可以被优化掉。
我们在处理2k-1个的时候,可以用一个Min[i]表示前i个位置最小的代价。
这样,在之后dp的转移中,我们不需要遍历前i-k这段以"各种字母"结尾代价的最小值,直接用Min[i-k]即可,在dp转移完之后,顺带更新Min[i] = min(Min[i], dp[i][j]);
这样就将时间复杂度优化到O(n*m)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 10;
const int INF = 1e9 + 7;
int n, m, k;
char s[maxn];
int Map[30][30];///Map[i][j]表示字母i变成字母j的代价
int w[maxn][30];
///w[i][j]表示前i个都变成字母j的代价
///w[i][j] = w[i - 1][j] + Map[s[i - 1] - ‘a‘ + 1][j]
int dp[maxn][30];
///dp[i][j]表示在前i个中,第i个位置为字母j的最小代价
///此时要保证字符串是k连击 所以只存在以下两种情况
///1 i-1为字母j
///2 i-k+1 到 i 全都是字母j
int Min[maxn];///Min[i]表示前i个最小代价
int main()
{
scanf("%d%d%d", &n, &m, &k);
scanf("%s", s);
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &Map[i][j]);
for (int k = 1; k <= m; k++)
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++)
Map[i][j] = min(Map[i][j], Map[i][k] + Map[k][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
w[i][j] = w[i - 1][j] + Map[s[i - 1] - ‘a‘ + 1][j];
for (int i = 1; i <= min(n, 2 * k - 1); i++)///长度小于2*k-1 只能是同一个字母
{
Min[i] = INF;
for (int j = 1; j <= m; j++)
dp[i][j] = w[i][j], Min[i] = min(Min[i], dp[i][j]);
}
for (int i = 2 * k; i <= n; i++)
{
Min[i] = INF;
for (int j = 1; j <= m; j++)
{
///直接和上一位相同
dp[i][j] = dp[i - 1][j] + Map[s[i - 1] - ‘a‘ + 1][j];
dp[i][j] = min(dp[i][j], Min[i - k] + w[i][j] - w[i - k][j]);///加上i-k+1到i都是j的代价
Min[i] = min(Min[i], dp[i][j]);
}
}
int ans = INF;
for (int i = 1; i <= m; i++)
ans = min(ans, dp[n][i]);
cout << ans << endl;
return 0;
}
以上是关于Moortal Cowmbat的主要内容,如果未能解决你的问题,请参考以下文章