Description
桌子上有2N+2个位置,其中前N个位置放着N个正面朝上的硬币,接下来N个位置放着反面朝上的硬币,后面两个位置为空。
每次操作可以将任意两个相邻的硬币顺序不变地移动到空位上,那么至少要多少次操作可以将硬币移成正反相间(正面的硬币在第一个,最后两个还是空)。
例如:当N = 4时,有一组合法解如下:
11110000__
__11000011
101__00011
1010100__1
10101__001
10101010__
Input Format
输入一个n
Output Format
第一行输出最少移动步数
接下来一行为移动方案,只要输出被移动的两个非空格格子左边那个的编号。换不换行无所谓。详见样例。
Sample Input
4
Sample Output
5 1 4 8 6 9
Hint
对于100%的数据,2<n<=200000
分析:
首先需要先找到最少步数与N的关系,在考场上找到的是当N为偶数时,最少步数F(N) = N * 2 - 3,当N为奇数时,F(N) = N * 2 - 2,然后发现构造过程也很容易,随便搞搞就可以了。
后来成绩出来,发现这一题没分。而且,全场都没有人有拿到分。。。很多人想到是N + 1,但是没有人构造出过程。
回来之后,写了个暴力,只跑到N = 8的情况(再大内存受不了),发现答案确实是N + 1。
一个学长说这很像ACM WORLD FINAL 2014的第一题,就去研究了一下,但是按那道题的方法最终的结果是0101……也没有想到怎么将它转换为1010……
后来CJK花了一整天的时间研究出一些思路,大致是分解成子问题,每次规模减小,后来又一步步完善,之后变成这样:
对于一个类似11111111000000__00的子问题,可以转换为10101010__10101010(__可以在任意0和1之间,但是不能是1__0),步骤如下:
11111111000000__00
1__111110000001100
1001(111100__00)1100 (括号内表示下一个子问题)
子问题解决之后,再经过下面的转换:
1001(1010__1010)1100
1001(1010101010)1__0
10__(1010101010)1010
这样就将当前的子问题解决了且子问题规模每次缩小4,而每次增加的步数也是4,那么就可以符合我们的要求。
由于N > 2,所以我们的问题要从3开始处理(而且1本身就是结果,2无法解决),3、4、5、6无法再分解为子问题,所以要内置3、4、5、6的解法和对应子问题的解法。
然后我又在暴力的基础上加了输出所有解,找出了3、4、5、6的可行方案,分别是:
3:2 6 4 7
4:1 4 8 6 9
5:2 7 11 3 8 11
6:1 6 12 9 3 10 13
然后子问题的解决方案:
3:6 2 5
4:8 1 4 7
5:8 3 11 2 7
6:10 2 12 9 6 3
以3来举个例子,当规模为3的子问题出现的时候,应该是这样:
1__1(11100000)1100
然后第一步将第6、7个位置上的0移开,变成1001(11100__0)1100
接下来依次操作:
1001(1__00110)1100
1001(1010__10)1100
然后就可以解决外面的子问题了。
现在需要确定操作的规律,我们定义当前子问题的规模为N,起点为L,上一步操作为M,任意举一个例子N = 11的情况:
1111111111100000000000__
11111111111000000000__00 N = 11 L = 0 M = 21 = L + N * 2 - 1
1__111111110000000001100 N = 11 L = 0 M = 2 = L + 2
1001(111111100000__00)1100 N = 7 L = 4 M = 17 = L + N * 2 - 1
1001(1__1111000001100)1100 N = 7 L = 4 M = 6 = L + 2
1001(1001(11100__0)1100)1100 N = 3 L = 8 M = 14 = L + 6
1001(1001(1__00110)1100)1100 N = 3 L = 8 M = 10 = L + 2
1001(1001(1010__10)1100)1100 N = 3 L = 8 M = 13 = L + 5
1001(1001101010101__0)1100 N = 7 L = 4 M = 18 = L + N * 2
1001(10__101010101010)1100 N = 7 L = 4 M = 7 = L + 3
100110101010101010101__0 N = 11 L = 0 M = 22 = L + N * 2
10__10101010101010101010 N = 11 L = 0 M = 3 = L + 3
1010101010101010101010__ N = 11 L = 0 M = 23 = L + N * 2 + 1
从上面的过程中,我们可以发现一些规律,每次缩小规模时,解决子问题之前的操作都是L + N * 2 - 1 和 L + 2,而解决子问题之后的操作都是L + N * 2和L + 3,中间规模为3~6的子问题特殊解决,然后大问题的最后一部都是将末尾两个移到前面,因为解决规模为3~6的子问题需要的步骤分别是3~6,加上最后一步,需要的操作数刚好是N + 1。
代码:
1 #include <cstdio> 2 int n, l; 3 int main () 4 { 5 scanf ("%d", &n); 6 printf ("%d\n", n + 1); 7 if (n < 7) 8 { 9 switch (n) 10 { 11 case 3: printf ("2 6 4 7 "); break; 12 case 4: printf ("1 4 8 6 9 "); break; 13 case 5: printf ("2 7 11 3 8 11 "); break; 14 case 6: printf ("1 6 12 9 3 10 13 "); break; 15 } 16 return 0; 17 } 18 for (l = 0; n >= 7; n -= 4, l += 4) 19 printf ("%d %d ", l + (n << 1) - 1, l + 2); 20 switch (n) 21 { 22 case 3: printf ("%d %d %d ", l + 6, l + 2, l + 5); break; 23 case 4: printf ("%d %d %d %d ", l + 8, l + 1, l + 4, l + 7); break; 24 case 5: printf ("%d %d %d %d %d ", l + 8, l + 3, l + 11, l + 2, l + 7);break; 25 case 6: printf ("%d %d %d %d %d %d ", l + 10, l + 2, l + 12, l + 9, l + 6, l + 3); break; 26 } 27 for (l -= 4, n += 4; l >= 0; l -= 4, n += 4) 28 printf ("%d %d ", l + (n << 1), l + 3); 29 n -= 4; 30 printf ("%d ", (n << 1) + 1); 31 }