计蒜客某次比赛,原题链接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;
}
可以看出来应该是。由于数据集最大为,自然的结果时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、序列为 1 开头,次数为。
我们用上面的例子来说明,序列 1001101101101101110110,其中 1 有 7 块,哪么可以知道最少的次数应该就是。
//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
@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