题目地址:
https://www.luogu.com.cn/problem/P2708
题目描述:
有很多个硬币摆在一行,有正面朝上的,也有背面朝上的。正面朝上的用
1
1
1表示,背面朝上的用
0
0
0表示。现在要求从这行的第一个硬币开始,将从第一个硬币开始的前若干个硬币同时翻面,求如果要将所有硬币翻到正面朝上,最少要进行这样的操作多少次?
输入格式:
一个字符串,由
0
0
0和
1
1
1组成,表示硬币状态。
输出格式:
一个整数,表示要翻转的最少次数。
代码如下:
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
while (s.back() == '1') s.pop_back();
if (!s.length()) {
puts("0");
return 0;
}
// 事实上下面的循环在求,去掉s末尾所有的1删除后,
// 继续删除中间重复的连续的0或1,只保留一个,此时s的长度即为答案
int t = 1;
for (int i = 1; i < s.length(); i++)
if (s[i] != s[i - 1])
t++;
cout << t << endl;
return 0;
}
时间复杂度 O ( n ) O(n) O(n)。
算法正确性证明:
设
f
(
c
1
,
c
2
,
.
.
.
,
c
n
)
f(c_1,c_2,...,c_n)
f(c1,c2,...,cn)为对于硬币序列
(
c
1
,
.
.
.
,
c
n
)
(c_1,...,c_n)
(c1,...,cn)所需翻转的最少次数。
先证明:若
c
n
=
1
c_n=1
cn=1,
f
(
c
1
,
c
2
,
.
.
.
,
c
n
)
=
f
(
c
1
,
c
2
,
.
.
.
,
c
n
−
1
)
f(c_1,c_2,...,c_n)=f(c_1,c_2,...,c_{n-1})
f(c1,c2,...,cn)=f(c1,c2,...,cn−1)。因为左右序列前
n
−
1
n-1
n−1个硬币状态相同,而左边多一个硬币,所以对于任意一个使得左边序列成为全朝上的操作序列
q
1
q
2
.
.
.
q
s
q_1q_2...q_s
q1q2...qs,将其中翻转
1
1
1 ~
n
n
n的操作都置换为翻转
1
1
1 ~
n
−
1
n-1
n−1,得到新的操作序列
q
1
′
q
2
′
.
.
.
q
s
′
q'_1q'_2...q'_s
q1′q2′...qs′,此序列就可以将右边的硬币序列翻为全朝上,所以
f
(
c
1
,
c
2
,
.
.
.
,
c
n
)
≥
f
(
c
1
,
c
2
,
.
.
.
,
c
n
−
1
)
f(c_1,c_2,...,c_n)\ge f(c_1,c_2,...,c_{n-1})
f(c1,c2,...,cn)≥f(c1,c2,...,cn−1)。由于
c
n
=
1
c_n=1
cn=1,所以任何一个把后者序列翻转为全朝上的操作,都可以将前者翻为全朝上,所以
f
(
c
1
,
c
2
,
.
.
.
,
c
n
)
≤
f
(
c
1
,
c
2
,
.
.
.
,
c
n
−
1
)
f(c_1,c_2,...,c_n)\le f(c_1,c_2,...,c_{n-1})
f(c1,c2,...,cn)≤f(c1,c2,...,cn−1)。所以
f
(
c
1
,
c
2
,
.
.
.
,
c
n
)
=
f
(
c
1
,
c
2
,
.
.
.
,
c
n
−
1
)
f(c_1,c_2,...,c_n) = f(c_1,c_2,...,c_{n-1})
f(c1,c2,...,cn)=f(c1,c2,...,cn−1)。既然如此,不妨设
c
n
=
0
c_n=0
cn=0,也就是将
s
s
s末尾的
1
1
1 pop掉。如果
s
s
s变为空,显然结果是
0
0
0。
设 c n = c n − 1 = . . . = c s = 0 ≠ c s − 1 = 1 c_n=c_{n-1}=...=c_s=0\ne c_{s-1}=1 cn=cn−1=...=cs=0=cs−1=1,由于末尾的背面朝上的硬币必然要翻,所以得到递推式 f ( c 1 , c 2 , . . . , c n ) = f ( ¬ c 1 , ¬ c 2 , . . . , ¬ c n ) + 1 = f ( ¬ c 1 , ¬ c 2 , . . . , ¬ c s − 1 ) + 1 f(c_1,c_2,...,c_n) = f(\neg c_1,\neg c_2,...,\neg c_n) + 1=f(\neg c_1,\neg c_2,...,\neg c_{s-1}) + 1 f(c1,c2,...,cn)=f(¬c1,¬c2,...,¬cn)+1=f(¬c1,¬c2,...,¬cs−1)+1(此处需要证明每个操作的先后次序是可以交换的。这本质上其实是二进制 n n n维向量加法的交换律,是显然成立的。所以不妨先进行翻转 1 1 1到 n n n的操作),这样把问题又变为末尾为 1 1 1的情况。不停递推下去,直到序列缩小为空。显然,由证明过程可以知道,对于硬币序列里任何长度连续的 1 1 1或 0 0 0,都可以等效的看为只有 1 1 1个。而对于 ( c 1 , c 2 , . . . , c n ) = ( 1 , 0 , 1 , 0 , . . . , n % 2 ) (c_1,c_2,...,c_n)=(1,0,1,0,...,n\%2) (c1,c2,...,cn)=(1,0,1,0,...,n%2)或 ( 0 , 1 , 0 , 1 , . . . , ( n + 1 ) % 2 ) (0,1,0,1,...,(n+1)\%2) (0,1,0,1,...,(n+1)%2),根据递推方程, f ( 1 , 0 , 1 , 0 , . . . , n % 2 ) = n − n % 2 f(1,0,1,0,...,n\%2)=n-n\%2 f(1,0,1,0,...,n%2)=n−n%2, f ( 0 , 1 , 0 , 1 , . . . , ( n + 1 ) % 2 ) = n − ( n + 1 ) % 2 f(0,1,0,1,...,(n+1)\%2)=n-(n+1)\%2 f(0,1,0,1,...,(n+1)%2)=n−(n+1)%2。
直观上理解,答案可以这样得到:先删除 s s s末尾的 1 1 1,然后数有多少段连续的 0 0 0和 1 1 1。段数即为答案。
下面举一个例子以方便理解:
f
(
11001011
)
=
f
(
110010
)
=
1
+
f
(
001101
)
=
1
+
f
(
00110
)
=
2
+
f
(
11001
)
=
2
+
f
(
1100
)
=
3
+
f
(
0011
)
=
3
+
f
(
00
)
=
4
+
f
(
11
)
=
4
f(11001011)=f(110010)=1+f(001101)\\=1+ f(00110)=2+f(11001)=2+f(1100)\\=3+f(0011)=3+f(00)=4+f(11)=4
f(11001011)=f(110010)=1+f(001101)=1+f(00110)=2+f(11001)=2+f(1100)=3+f(0011)=3+f(00)=4+f(11)=4容易看出来,
11001011
11001011
11001011去掉末尾的
1
1
1后,有四段:
11
,
00
,
1
,
0
11,00,1,0
11,00,1,0,答案就是
4
4
4。