【难度】
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 1≤n≤105
【输入样例】
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]表示前i位都翻转成0的最少次数dp[i][1]表示前i位都翻转成1的最少次数
状态转移也比较简单。
若
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[i−1][0],dp[i−1][1]+1}dp[i][1]=min{dp[i−1][0]+1,dp[i−1][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,i−k+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(最优)
11111011000→00000100000→00000000000(最优)
11111011000
→
11111010000
→
11111000000
→
00000000000
11111011000 \rightarrow 11111010000 \rightarrow 11111000000 \rightarrow 00000000000
11111011000→11111010000→11111000000→00000000000
(2)左边有不麻烦的子区间
00000100110
→
11111011000
→
00000100000
→
00000000000
(
无
差
别
)
00000100110 \rightarrow 11111011000\rightarrow 00000100000\rightarrow00000000000 (无差别)
00000100110→11111011000→00000100000→00000000000(无差别)
00000100110
→
00000100100
→
00000100000
→
00000000000
00000100110 \rightarrow 00000100100\rightarrow 00000100000\rightarrow00000000000
00000100110→00000100100→00000100000→00000000000
综上考虑,
k
≥
2
时
,
翻
转
区
间
[
1
,
i
]
总
是
优
于
单
独
翻
转
两
个
数
字
的
。
\color{red}k\ge2时,翻转区间[1,i]总是优于单独翻转两个数字的。
k≥2时,翻转区间[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;
}