D. Balanced String(保姆级)

Problem - D - Codeforces 

将交换操作变为构造目标串,通过比较与目标串的不同位数算出答案;

贡献值的计算方式可由其性质得,即对所有位置的 1 ,该 1 前 0 的数量减去该 1 后 0 的数量;(反之亦然)

需要注意,该题条件需同时满足总贡献为 0 且操作次数最少,dp 只能表示一种值的情况,故除了枚举构造到的位置 i 和已选的 1 的数量 j 外,还需对每个状态下的贡献值情况 k 枚举;

而显然 k 有可能是负贡献,故对 k 枚举时需要加上一个大常数 K 作为 0 贡献点,保证 dp 的该维不会出现负值;

而 k 是平方级枚举,例:00000000001111111111;

至此时间复杂度  O(n^{4}),但空间开出 10^{8} ;又 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多状态转移的注意事项、通过性质和构造思想为问题提供新的枚举角度从而解决;

故写一份详细题解做记;

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值