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的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段6——CSS选择器

VSCode自定义代码片段——声明函数