【洛谷】P2708 硬币翻转(配数学证明)

题目地址:

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,...,cn1)。因为左右序列前 n − 1 n-1 n1个硬币状态相同,而左边多一个硬币,所以对于任意一个使得左边序列成为全朝上的操作序列 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 n1,得到新的操作序列 q 1 ′ q 2 ′ . . . q s ′ q'_1q'_2...q'_s q1q2...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,...,cn1)。由于 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,...,cn1)。所以 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,...,cn1)。既然如此,不妨设 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=cn1=...=cs=0=cs1=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,...,¬cs1)+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)=nn%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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值