将交换操作变为构造目标串,通过比较与目标串的不同位数算出答案;
贡献值的计算方式可由其性质得,即对所有位置的 1 ,该 1 前 0 的数量减去该 1 后 0 的数量;(反之亦然)
需要注意,该题条件需同时满足总贡献为 0 且操作次数最少,dp 只能表示一种值的情况,故除了枚举构造到的位置 i 和已选的 1 的数量 j 外,还需对每个状态下的贡献值情况 k 枚举;
而显然 k 有可能是负贡献,故对 k 枚举时需要加上一个大常数 K 作为 0 贡献点,保证 dp 的该维不会出现负值;
而 k 是平方级枚举,例:00000000001111111111;
至此时间复杂度 ,但空间开出
;又 i 维上的转移单项连续,所以可将 i 维降至 2 维,利用 i 的奇偶性与 1 异或表示不同相邻项的转移;代价是每次更新 i 时需对本次要赋值的那一维初始化,时间复杂度常数乘以 2;
dp 值的转移过程仅需考虑在当前 i,j,k 的条件下选 1 或选 0 与原串不同则 +1;
#include <bits/stdc++.h>
using namespace std;
const int max_n=110,max_N=(max_n*max_n+10)*2;
int dp[2][max_n][max_N]; //单项连续转移将i维降至2维
void solve()
{
string s;cin>>s;
int n=s.size();
int cnt=0;
for(int i=0;i<n;i++){
if(s[i]=='1')cnt++; //可枚举的1的总个数
}
int K=n*n+10; //零点值位设为K避免负贡献RE
for(int i=0;i<2;i++)for(int j=0;j<max_n;j++)for(int k=0;k<max_N;k++)dp[i][j][k]=200;
dp[0][0][K]=0;
//由于枚举的是已经选完i项,第i+1项选1或者0,所以边界为已经选了0个数,0个数中拿了0个1,贡献值在零点K下的操作次数为0(没有与原串相不同的位置)
for(int i=0;i<n;i++){
int x=i&1; //x记录已经选到的数量,即i的奇偶性
for(int j=0;j<max_n;j++)for(int k=0;k<max_N;k++)dp[x^1][j][k]=200; //对即将赋值的x^1进行初始化
for(int j=0;j<=min(cnt,i);j++){ //可选的1的数量(这里不限制直接枚举到i也无妨)
for(int k=-(n*n);k<=n*n;k++){ //k的平方级贡献
if(dp[x][j][k+K]>100)continue;
//如果转移前(选1或0)之前就无合法项可以直接跳
dp[x^1][j+1][k+i-j+K]=min(dp[x^1][j+1][k+i-j+K],dp[x][j][k+K]+(s[i]!='1'));
//选了1,则j+1,定义01增加为正,则k+(i-j)为选1后的贡献,dp值是否+1取决于原串该位置是否为1
dp[x^1][j][k-j+K]=min(dp[x^1][j][k-j+K],dp[x][j][k+K]+(s[i]!='0'));
//同理,定义01增加为正则10增加为负
}
}
}
cout<<dp[n&1][cnt][K]/2;
//尽管最终枚举到的i为n-1,但维护到的项是i=n-1时的(i&1)^1,所以输出的是n&1位置,选了cnt个1,零点贡献K时的与原串不相同项的数量。交换操作一次换俩,故除以2即为答案
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
这题我学到不少:
连续转移降维优化空间、设大值K避免负值越界、dp多状态转移的注意事项、通过性质和构造思想为问题提供新的枚举角度从而解决;
故写一份详细题解做记;