熄灭灯泡问题(C语言蓝桥杯模拟题,时间复杂度仅O(n))

问题描述
小华面前有一排控制灯泡的开关,0表示关,1表示开,他每次会选择一个位置n,然后把 n位置的开关全部都按一次。给定开关的初始状态,用 01字符串表示,长度不超过100000,小华至少选几次,把所有的灯泡都熄灭(即所有开关都为0)?
样例输入

100
1010011
样例输出

1
5


举例子解释一下题目:

假设有5个灯泡。状态是01010,如果把灯泡全部熄灭,那就是全部变成00000。那么,如何选择位置呢?
因为每选择一次位置n,都会让第1个到第n个的灯泡的状态发生改变,所以,每次都从最前面开始选择,一步一步地让灯泡逐步变成相同状态,以此类推最后一次全部灯泡都从1状态变成0状态。

example 1:
初始:01010
第一次:11010(选择第一个位置)
第二次:00010(选择第二个位置)
第三次:11110(选择第三个位置)
第四次:00000(选择第四个位置)
选择次数为4。

example 2:
初始:1011101
第一次:0011101(选择第一个位置)
第二次:1111101(选择第二个位置)
第三次:0000001(选择第五个位置)
第四次:1111111(选择第六个位置)
第五次:0000000(选择第七个位置)
选择次数为5。

用程序验证答案
如果不把前面的灯泡先变成相同的状态而直接跳过不同状态的灯泡选择后面的,必然会导致有的灯泡要重复做着无用的状态改变。例如情况“000100110”时,不选择第四个位置的“1”而直接选到第五个位置的“0”,就会变成这样:“111010110”。不难发现前五个位置的灯泡状态由“00010”变成“11101”,相对状态并没有改变,所以此次选择是无意义的。


既然这道题是要把1变成0,而且一次是改变一串。以example 2为例,第三第四第五个的“1”可以在一次选择中全部改变成“0”。这就意味着,任何情况都可以化简为“010101”或者“101010”的形式,也就是说,两个“0”之间无论有多少个“1”都可以化简为一个“1”,两个“1”之间无论有多少个“0”都可以化简为一个“0”。例如:“11000111111011001”就可以化简为“1010101”。

example 3:
初始:0011110110100111110011
化简后:0101010101
第一次:1101010101
第二次:0001010101
……
第九次:1111111111
第十次:0000000000
用程序验证答案
例子初始值经过化简后,变成5个“0”和5个“1”相间的字符串。

回顾一下example 1,整个字符串的选择次数和化简后的字符串的“1”或者“0”的数量关系或许可以用数学公式来表达(下面以“1”为例)。

//化简思想代码:
    char s[100000];
    int num;//记录字符串长度
    int flag;//记录化简后'1'的数量
    scanf("%s",s);
	num=strlen(s);//读取字符串长度
	for(i=0;i<num; ){
		if(s[i]=='1'){
		i++;//往下一个位置读取
		while(s[i]=='1')
		i++;//while//往下一个位置读取
		flag++;//记录下化简后有多少个“1”;
		}//if1
		else
		i++;//往下一个位置读取
	}//for

example 4:
初始:01010
第一次:11010
第二次:00010
第三次:11110
第四次:00000
“1”的数量:2
选择次数:4

example 5:
初始:010100110
化简:0101010
第一次:1101010
……
第五次:1111110
第六次:0000000
化简后“1”的数量:3
选择次数:6


嗯哼?莫非<选择次数=“1”的数量×2>?这难道只是个巧合吗?
那我们再来试几个例子:

example 6:
初始:0101
第一次:1101
第二次:0001
第三次:1111
第四次:0000
"1"的数量:2
选择次数:4

初始状态:1010
第一次:0010
第二次:1110
第三次:0000
“1”的数量:2
选择次数:3

初始:01010101
“1”的数量:4
选择次数:8

初始:10101010
“1”的数量:4
选择次数:7

初始:01
“1”的数量:1
选择次数:2

初始:10
“1”的数量:1
选择次数:1


其实啊,这个算法的整体的关键就在于,当化简后,最后有n个亮着的灯泡,要最后一个灯泡熄灭,那么最前方第一个灯泡就得变化2n次。

计算公式

当然,如果说次数是“1”化简后的次数的两倍明显是有失偏颇的。但是同样是m个“1”,有的情况是2m次,有的情况是2m-1次。那么为什么有的情况是奇数次,有的情况是偶数次呢?
至于相差1次,那就只需要看最前方第一个两者的灯泡的位置是在第一位还是第二位判别着看就行。
如果是“0”开头,若想要后边的“1”变成“0”,则必须先把开头的“0”先变成“1”,才能把开头的“0”后面的“1”变成“0”。而如果第一个灯泡是“1”,则直接就可以让它变成“0”,就可以直接少选择一次。而这个情况,无论字符串多长,都适用。

//附上判断计算代码:
	if(s[0]=='1')
	sum=flag*2-1;
	if(s[0]=='0')
	sum=flag*2;

附上完整代码:

最后,附上完整代码。敬请批评指正!

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int main()
{
	char s[1000000];
	while(~scanf("%s",s))
	{	
	int num,sum,i=0,flag=0;
	num=strlen(s);
	for(i=0;i<num; ){
		if(s[i]=='1'){
		i++;
		while(s[i]=='1')
		i++;//while
		flag++;
		}//if1
		else
		i++;
	}
	if(s[0]=='1')
	sum=flag*2-1;
	if(s[0]=='0')
	sum=flag*2;
	cout<<"选择次数为:";
	cout<<sum<<endl;
	}
	return 0;
 } 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芣苢的成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值