【牛客练习赛67:贪心/DP】D:牛妹爱数列

牛客练习赛67:D题 牛妹爱数列

【难度】

3 / 10 3/10 3/10
不是很难

【题意】

给你一个长度为 n n n 的01串。
你有两种操作:

操作1:某一位转换(1变0,0变1)
操作2:区间转换,区间 [ 1 , K ] [1,K] [1,K]中的全部数字都转换(1变0,0变1)

问你最少的操作次数,使得整个串全为0

【数据范围】

1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105

【输入样例】

10
1 0 1 1 0 0 0 1 0 0

【输出样例】

3

【解释】

样例解释:
第一次使用(1)操作, 把2改掉: 1 1 1 1 0 0 0 1 0 0
第二次使用(2)操作, 把1-4全部改掉: 0 0 0 0 0 0 0 1 0 0
第三次使用(1)操作, 把8改掉: 0 0 0 0 0 0 0 0 0 0

【思路】

【思路一:DP】
( 出自队友禾硕 )
定义dp[ i ][ j ]为:
d p [   i   ] [   0   ] 表 示 前 i 位 都 翻 转 成 0 的 最 少 次 数 d p [   i   ] [   1   ] 表 示 前 i 位 都 翻 转 成 1 的 最 少 次 数 dp[\,i\,][\,0\,]表示前i位都翻转成0的最少次数\\ dp[\,i\,][\,1\,]表示前i位都翻转成1的最少次数\\ dp[i][0]i0dp[i][1]i1
状态转移也比较简单。
s [ i ] = 0 s[i]=0 s[i]=0那么:
d p [ i ] [ 0 ] = min ⁡ { d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + 1 } d p [ i ] [ 1 ] = min ⁡ { d p [ i − 1 ] [ 0 ] + 1 , d p [ i − 1 ] [ 1 ] + 1 } dp[i][0]=\min \Big \{dp[i-1][0],dp[i-1][1]+1\Big \}\\ dp[i][1]=\min \Big \{dp[i-1][0]+1,dp[i-1][1]+1\Big \}\\ dp[i][0]=min{dp[i1][0],dp[i1][1]+1}dp[i][1]=min{dp[i1][0]+1,dp[i1][1]+1}
当然, s [ i ] = 1 s[i]=1 s[i]=1时的dp状态 也可以简单地得到。思路同上。

【思路二:贪心】

我们从右向左枚举
当前枚举到第 i i i 位。
若遇到的数字为0,不管它。
若遇到一个1,该数字与其左右两边都不同,则单独翻转它。
否则,即遇到一群的1(至少两个,我们设为 k k k 个),我们翻转区间 [ 1 , i ] [1,i] [1,i]

至于这样为什么是正确的,我们稍微yy一下:
若 我 们 翻 转 区 间 [ 1 , i ] , 再 翻 转 区 间 [ 1 , i − k + 1 ] , 与 单 独 翻 转 两 个 1 是 等 价 的 若 我 们 翻 转 区 间 [ 1 , i ] , 左 边 可 能 有 一 些 更 麻 烦 的 区 间 被 我 们 解 决 了 。 \color{white}若我们翻转区间[1,i],再翻转区间[1,i-k+1],与单独翻转两个1是\color{blue}等价的\\ \color{white}若我们翻转区间[1,i],左边可能有一些更麻烦的区间被我们解决了。 [1,i][1,ik+1]1[1,i]

更麻烦的区间是指 1111101111 1111101111 1111101111
若我们单独操作,需要翻转两次才能使之全0。
但是若我们翻转区间 [ 1 , i ] [1,i] [1,i] 的时候,存在一个子区间 [ 1 , m ] [1,m] [1,m]使得它为麻烦的区间
那么麻烦的区间会变成 0000010000 0000010000 0000010000,即只需简单翻转一次即可。

若我们遇到了不麻烦的子区间 [ 1 , m ] [1,m] [1,m],比如 00000100000 00000100000 00000100000
我们在翻转 [ 1 , i ] [1,i] [1,i]时,该不麻烦的子区间变为了麻烦的子区间。
但是这样的操作并不会使得总次数增加
【两个例子】
每个例子第一种为优先区间翻转操作,第二种为优先个别翻转操作。
(1)左边有麻烦的子区间
11111011000 → 00000100000 → 00000000000 ( 最 优 ) 11111011000 \rightarrow 00000100000\rightarrow 00000000000(最优) 111110110000000010000000000000000
11111011000 → 11111010000 → 11111000000 → 00000000000 11111011000 \rightarrow 11111010000 \rightarrow 11111000000 \rightarrow 00000000000 11111011000111110100001111100000000000000000
(2)左边有不麻烦的子区间
00000100110 → 11111011000 → 00000100000 → 00000000000 ( 无 差 别 ) 00000100110 \rightarrow 11111011000\rightarrow 00000100000\rightarrow00000000000 (无差别) 00000100110111110110000000010000000000000000
00000100110 → 00000100100 → 00000100000 → 00000000000 00000100110 \rightarrow 00000100100\rightarrow 00000100000\rightarrow00000000000 00000100110000001001000000010000000000000000

综上考虑,
k ≥ 2 时 , 翻 转 区 间 [ 1 , i ] 总 是 优 于 单 独 翻 转 两 个 数 字 的 。 \color{red}k\ge2时,翻转区间[1,i]总是优于单独翻转两个数字的。 k2[1,i]

稍微优化一下
我们每次都翻转一个区间,时间复杂度会很高。
我们设置一个翻转标记 b j bj bj,初始化为0,表示翻转的奇偶次数
b j bj bj s [ i ] s[i] s[i] 相同,那么等价于该位目前是0。
若进行了一次区间翻转,那么 b j   ∧ = 1 bj \,\wedge=1 bj=1

【核心代码】

时间复杂度: O ( N ) O(N) O(N)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
int aa[MAX];

int main()
{
    int n;
    scanf("%d",&n);
    
    for(int i=1;i<=n;++i)scanf("%d",&aa[i]);
    
    int bj = 0;
    int ans = 0;
    
    for(int i=n;i>=1;--i){
        if(aa[i]==bj)continue;
        
        int cnt = 0;
        while(i>=1 && aa[i]!=bj)--i,cnt++;
        i++;
        
        if(cnt==1)ans+=cnt;
        else {
            bj ^= 1;
            ans++;
        }
    }
    printf("%d",ans);
    return 0;
}

你可能感兴趣的:(算法,动态规划,贪心算法)