题目描述
难度分:845
输入n(2≤n≤2×105),长为n的01
字符串s,长为n的数组a(1≤a[i]≤109)。
修改s,使其中恰好包含一对相邻相同字符。例如10010
是符合要求的,11001
是不符合要求的(有两对相邻相同字符11
和00
)。
修改s[i]的代价是a[i]。输出最小总代价。
输入样例1
5
00011
3 9 2 6 4
输出样例1
7
输入样例2
4
1001
1 2 3 4
输出样例2
0
输入样例3
11
11111100111
512298012 821282085 543342199 868532399 690830957 973970164 928915367 954764623 923012648 540375785 925723427
输出样例3
2286846953
算法
前后缀分解+动态规划
状态定义
pre[i][k]表示结尾为k(k=0,1),且前缀[1,i]为01
交替串的修改代价。suf[i][k]表示开头为k,且后缀[i,n]为01
交替串的修改代价。
状态转移
以pre数组为例说明,初始情况下pre[0][0]=pre[0][1]=0(前缀为空不需要任何代价)。如果期望s[i]=0
,那前一个位置i−1就必须是1
,转移来源为pre[i−1][1]。当前s[i]=0
不需要代价,s[i]=1
需要付出c[i]的修改代价。状态转移方程为pre[i][0]=pre[i−1][1]+cost,同理如果期望s[i]=1
,则状态转移方程为pre[i][1]=pre[i−1][0]+cost,其中cost取决于s[i]是不是本来就为期望的字符。
枚举+前后缀分解
最后枚举相邻相等的位置(i,i+1)(i∈[1,n−1]),如果s[i]=s[i+1]=0
,那前缀[1,i−1]就是以1
结尾的01
交替串,后缀[i+2,n]就是以1
开头的01
交替串;如果s[i]=s[i+1]=1
,那前缀[1,i−1]就是以0
结尾的01
交替串,后缀[i+2,n]就是以0
开头的01
交替串。维护min(pre[i−1][0]+suf[i+2][0]+cost1,pre[i−1][1]+suf[i+2][1]+cost0)的最小值即可,其中costk是将s[i]和s[i+1]都变为k(k=0,1)的代价。
复杂度分析
时间复杂度
动态规划的预处理时间复杂度为O(n)(状态数量O(n),单次转移O(1));枚举两个相邻的相等位置时间复杂度为O(n),更新答案时间复杂度为O(1)。因此,整个算法的时间复杂度还是O(n)。
空间复杂度
除了输入的s串和c数组,空间瓶颈就在于两个DP
数组pre和suf,都是O(n)的,这也是整个算法的额外空间复杂度。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int n, c[N];
char s[N];
LL pre[N][2], suf[N][2];
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
for(int i = 1; i <= n; i++) {
scanf("%d", &c[i]);
}
suf[n + 1][0] = suf[n + 1][1] = 0;
for(int i = 1, j = n; i <= n; i++, j--) {
pre[i][0] = pre[i - 1][1] + (s[i] != '0'? c[i]: 0);
pre[i][1] = pre[i - 1][0] + (s[i] != '1'? c[i]: 0);
suf[j][0] = suf[j + 1][1] + (s[j] != '0'? c[j]: 0);
suf[j][1] = suf[j + 1][0] + (s[j] != '1'? c[j]: 0);
}
LL ans = 1e18;
for(int i = 1; i < n; i++) {
ans = min(ans, pre[i - 1][0] + suf[i + 2][0] + (s[i] == '1'? 0: c[i]) + (s[i + 1] == '1'? 0: c[i + 1]));
ans = min(ans, pre[i - 1][1] + suf[i + 2][1] + (s[i] == '0'? 0: c[i]) + (s[i + 1] == '0'? 0: c[i + 1]));
}
printf("%lld\n", ans);
return 0;
}