D2. Zero-One (Hard Version)
题意:给定两个长度为
n
n
n的01串
a
,
b
a,b
a,b,每次可以选择一个数对
(
l
,
r
)
(l,r)
(l,r),令
a
l
,
a
r
a_{l},a_{r}
al,ar都异或1。如果
l
+
1
=
=
r
l+1==r
l+1==r,花费
x
x
x,否则,花费
y
y
y。求让
a
a
a变为
b
b
b的最小花费,
(
n
<
=
5000
)
(n<=5000)
(n<=5000)。
参考
思路:
·对于特殊情况以及
y
≤
x
y \le x
y≤x的情况按D1的解法来。
·对于
x
<
y
x<y
x<y的情况考虑采取
O
(
N
2
)
O(N^2)
O(N2) 的 dp。
状态定义:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示区间
[
i
,
j
]
[i,j]
[i,j]的最小花费。
状态转移:区间
[
i
,
j
]
[i,j]
[i,j]的最优解只会由
d
p
[
i
+
1
]
[
j
−
1
]
dp[i+1][j-1]
dp[i+1][j−1]或
d
p
[
i
+
2
]
[
j
]
dp[i+2][j]
dp[i+2][j]或
d
p
[
i
]
[
j
−
2
]
dp[i][j-2]
dp[i][j−2]转移而来。
原因:
当x<y时,我们应尽可能的取更多的x,即保留更多的相邻位置。
栗子:
考虑
a
a
a和
b
b
b有六个位置不一样 _ _ _ _ _ _
对于
d
p
[
0
]
[
5
]
dp[0][5]
dp[0][5],只能通过
d
p
[
1
]
[
4
]
dp[1][4]
dp[1][4]或
d
p
[
0
]
[
3
]
dp[0][3]
dp[0][3]或
d
p
[
2
]
[
5
]
dp[2][5]
dp[2][5]转移过来,这样才能保证保住了更多的相邻位置。
这题的做法有点类似区间dp,但是不像常规的区间dp需要枚举区间断点,因为最优状态只是从有限个小区间转移过来,故复杂度是
O
(
N
2
)
O(N^2)
O(N2)。
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
int T,n,x,y;
string a,b;
int cal(int l,int r){
if(l+1==r) return min(2*y,x);
else return min(y,(r-l)*x);
}
int dp[5003][5003];
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n>>x>>y>>a>>b;
//预处理出不相等的位置
vector<int> v;
for(int i=0;i<n;++i){
if(a[i]!=b[i]) v.pb(i);
}
//特判一些情况
if(v.size()==0) cout<<0<<'\n';
else if(v.size()%2) cout<<-1<<'\n';
else if(v.size()==2){
cout<<cal(v[0],v[1])<<'\n';
}
else if(y<=x) cout<<v.size()/2*y<<'\n';
else{
//在x<y时,这种dp才有效
//直接用memset初始化会寄
for(int i=0;i<v.size();++i)
for(int j=0;j<v.size();++j)
dp[i][j]=0;
//防止后面-2导致越界,所以先预处理出长度为2的情况
for(int i=0;i+2-1<v.size();++i){
int j=i+2-1;
dp[i][j]=cal(v[i],v[j]);
}
for(int len=3;len<=v.size();len++){
for(int i=0;i+len-1<v.size();++i){
int j=i+len-1;
dp[i][j]=dp[i+1][j-1]+cal(v[i],v[j]);
dp[i][j]=min(dp[i][j],dp[i][j-2]+cal(v[j-1],v[j]));
dp[i][j]=min(dp[i][j],dp[i+2][j]+cal(v[i],v[i+1]));
}
}
cout<<dp[0][v.size()-1]<<'\n';
}
}
}