字节–字母交换
一、题目描述
字符串S由小写字母构成,长度为n
。定义一种操作,每次都可以挑选字符串中任意的两个相邻字母进行交换
。询问在至多交换m次之后,字符串中最多有多少个连续的位置上的字母相同
?
-
输入描述:
第一行为一个字符串S与一个非负整数m。(1 <= |S| <= 1000, 1 <= m <= 1000000)
-
输出描述:
一个非负整数,表示操作之后,连续最长的相同字母数量。
输入例子1:
abcbaa 2
输出例子1:
2
例子说明1:
使2个字母a连续出现,至少需要3次操作。即把第1个位置上的a移动到第4个位置。
所以在至多操作2次的情况下,最多只能使2个b或2个a连续出现。
二、分析
- 这题算是这场笔试最难的题了,
区间动态规划
。 - 要求移动后形成的最长连续子串,这个最长连续子串可能全是a或b……c。因此,这里
需要枚举移动后形成的最长连续子串里面所包含的字母
; -
确定了里面包含的字母,就可以专注于这个字母了,也就是说其余的字母都是没有用的,把它们从序列中挖掉;
- 然后就值剩下目标字母了,目标字母离散地分布在序列中,因此,再离散化一下,搞完之后会生成一个行的序列,之后的动态规划就在新的序列上进行。
- 下面的图片表达了上述过程:
- 现在新的序列pos看起来是合在了一起,形成了最长连续子序列,但是,形成这些连续序列所需要的操作次数是多少呢?
如果操作次数大于m,那么该序列就是不满足要求的;
- 因此,这里面就可以得出
区间动态规划了,先从小到大枚举段长,依次求得该段长的所有子序列的操作次数,并判断是否小于等于m,如果满足要求,就更新答案
。 - 从小到大枚举段长是为了利用子问题的结果;
dp[i][j]表示把pos[i]和pos[j]之间的目标字母移动到一起,形成j - i + 1长度的连续子序列所需要的操作次数
; - 状态转移方程:
dp[i][i + len - 1] = dp[i + 1][i + len - 2] + pos[i + len - 1] - pos[i] - len + 1
; - 依据是|x−a|+|x−b||x−a|+|x−b|在什么时候取得最小值。用
最小的移动次数把两个目标字母移动到一起的方法就是把两个目标字母都往中间靠
,状态转移方程就是根据这个来的 - 先
把pos[i + 1] ~ pos[i + len - 2]之间的目标字母移动到一起
,这个移动次数就是dp[i + 1][i + len - 2],然后把两个端点pos[i]和pos[i + len -1]处的目标字母往中间靠,所需要的移动次数是pos[i + len - 1] - pos[i] - len + 1
。
三、代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s;
int m;
while (cin >> s >> m)
{
int ans = 1;
//枚举每一个字符
for (char c = 'a'; c <= 'z'; c++)
{
//构建pos数组
vector<int> pos;
for (int i = 0; i < (int)s.size(); i++)
if (c == s[i])
pos.push_back(i);
//过滤掉只出现一次的字符
if (pos.size() < 2)
continue;
//dp数组
int ret = 1;
vector<vector<int> > dp(pos.size(), vector<int>(pos.size(), 0));
for (int len = 2; len <= (int)pos.size(); ++len)
{
for (int i = 0; i + len - 1 < (int)pos.size(); i++)
{
dp[i][i + len - 1] = dp[i + 1][i + len - 2] + pos[i + len - 1] - pos[i] - len + 1;
if (dp[i][i + len - 1] <= m)
ret = len;
}
}
ans = max(ans, ret);
}
cout << ans << endl;
}
return 0;
}