计蒜客题解——硬币

题目链接

计蒜客某次比赛,原题链接https://nanti.jisuanke.com/t/42257。

我自己的OJ,http://47.110.135.197/problem.php?id=5149。

题面

小 B 面前的桌子上有 n 个硬币,0 表示正面,1 表示反面,只有当这 n 个硬币都是 0 朝上的时候这个他才能把这些钱收起来。现在他可以一个这样的操作来翻硬币,他选择一个 x,把 1 到 x 位置上的硬币都翻面,他现在想知道最少需要多少次操作能使所有硬币正面朝上。

输入

第一行一个整数表示 n。
第二行一个长度为 n 的字符串 s,其中 s 的第 i 个字符 si 表示硬币 i 的状态。

输出

输出最小操作次数。

样例输入

4
1001

样例输出

3

数据范围

对于 30% 的数据,1 ≤ n ≤ 20;

对于另外 20% 的数据,si 全部相等;

对于 100% 的数据,1 ≤ n ≤ 10^6。

题目分析

把 1 到 x 位置上的硬币都翻面,直到所有的硬币都是正面朝上,求最少次数。

可以考虑用模拟,或者用动态规划。这次我们用模拟来分析题目。

先不考虑数据范围,我们来看一下,如何使用模拟。我们假设一个输入来分析一下整个过程,直接使用题目提供的样例输入。下面是整个过程。

1001 -> 0110 -> 1000 -> 0000

就是我们选择第 4 个硬币开始,第一次 1001 变成 0110。然后我们选择第 3 个硬币开始,第二次 0110 变成 1000。再选择第 1 个硬币开始,第三次 1000 变成 0000。这样就将所有的硬币变成了正面朝上。找到规律没有?我们选择一个长一点的序列。

1001101101101101110110 -> 
0110010010010010001000 ->
1001101101101101110000 ->
0110010010010010000000 ->
1001101101101100000000 ->
0110010010010000000000 ->
1001101101100000000000 ->
0110010010000000000000 ->
1001101100000000000000 ->
0110100000000000000000 ->
1001000000000000000000 ->
0110000000000000000000 ->
1000000000000000000000 ->
0000000000000000000000

我们发现最小次数的方法应该是:

1、从最后一个 1 开始翻。

2、每次 1 变 0,0 变 1。

3、连续的数据会一起发生变化。

这样我们的模拟算法基本思路就完成了。

模拟算法

代码

下面是使用模拟的算法代码。

//5149标准程序
#include 

const int MAXN = 1e6+2;
int data[MAXN] = {};//当前数据

//从后向前找第一个一
int locate(int len) {
    int i;
    int ret = -1;
    for (i=len; i>=1; i--) {
        if (1==data[i]) {
            return i;
        }
    }

    return -1;
}

//反转
void reverse(int len) {
    int i;
    for (i=1; i<=len; i++) {
        if (0 == data[i]) {
            data[i] = 1;
        } else {
            data[i] = 0;
        }
    }
}

int main() {
    //freopen("data.in", "r", stdin);
    int n;
    scanf("%d", &n);

    int i, j;
    int idx = -1;//
    char ch;
    for (i=1; i<=n; i++) {
        scanf(" %c", &ch);
        data[i] = ch - '0';
    }

    //
    unsigned long long ans = 0;
    idx = n;
    while (-1 != (idx = locate(idx)))  {
        reverse(idx);
        ans++;
    }

    printf("%llu\n", ans);
    //fclose(stdin);

    return 0;
}

复杂度

可以看出来应该是O(n * m)。由于数据集最大为10^{6},自然的结果时TLE。

算法优化

没关系,我们的标程就有了,哪么我们来优化算法。我们来分析数学。我们发现每次翻转的时候规律为 0 变 1,1 变 0,我们知道 0 和 1 的组合有 2 种。

1、01 这个组合。也就是说,0 数字后面如果存在 1,哪么这段数字变成全部 0,需要翻 2 次。什么意思,看下面的例子。

01 -> 10 -> 00

2、10 这个组合。只需要 1 次就可以完成翻转。

10 -> 00

这样我们发现规律,本题只需要统计整个序列中,到底有几块连续的数字 1。假设一个序列中连续数字 1 的块数为 n。

1、序列为 0 开头,次数为2\ast n

2、序列为 1 开头,次数为2\ast n-1

我们用上面的例子来说明,序列 1001101101101101110110,其中 1 有 7 块,哪么可以知道最少的次数应该就是2\ast 7 - 1= 13

AC代码

//5149
//http://47.110.135.197/problem.php?id=5149
#include 

int main() {
    //freopen("data.in", "r", stdin);
    int n;
    scanf("%d", &n);

    int i;
    char ch;
    bool flag = false;
    bool first = false;//第一个数字。0为false,1为true
    unsigned int ans = 0;
    for (i=0; i

辅助代码

随机数据代码

//生成数据 5149
#include 
#include 
#include 

int main(int argc, char *argv[]) {
    int seed = time(NULL);
    if (argc>1) {
        //有参数
        seed = atoi(argv[1]);
    }
    srand(seed);

    //生成长度
    const int lenMax = 1e6;
    const int lenMin = 1;
    int len = lenMin + rand()%(lenMax-lenMin+1);
    printf("%d\n", len);

    int i;
    for (i=0; i

Win对拍脚本

@echo off
set cnt = 1
:loop
	echo ==第%cnt%次测试
	set /a cnt = %cnt% + 1
	gen.exe %random% > data.in
	std.exe < data.in >std.out
	5149.exe < data.in > my.out
	fc my.out std.out
if not errorlevel 1 goto loop
pause
goto loop

 

你可能感兴趣的:(OJ题解,#,计蒜客题解)