【FJOI2015】金币换位问题

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为偶数时,最少步数FN) = N * 2 - 3,当N为奇数时,FN) = N * 2 - 2,然后发现构造过程也很容易,随便搞搞就可以了。

后来成绩出来,发现这一题没分。而且,全场都没有人有拿到分。。。很多人想到是N + 1,但是没有人构造出过程。

回来之后,写了个暴力,只跑到N = 8的情况(再大内存受不了),发现答案确实是N + 1

一个学长说这很像ACM WORLD FINAL 2014的第一题,就去研究了一下,但是按那道题的方法最终的结果是0101……也没有想到怎么将它转换为1010……

后来CJK花了一整天的时间研究出一些思路,大致是分解成子问题,每次规模减小,后来又一步步完善,之后变成这样:

对于一个类似11111111000000__00的子问题,可以转换为10101010__10101010__可以在任意01之间,但是不能是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无法解决),3456无法再分解为子问题,所以要内置3456的解法和对应子问题的解法。

然后我又在暴力的基础上加了输出所有解,找出了3456的可行方案,分别是:

32 6 4 7

41 4 8 6 9

52 7 11 3 8 11

61 6 12 9 3 10 13

然后子问题的解决方案:

36 2 5

48 1 4 7

58 3 11 2 7

610 2 12 9 6 3

3来举个例子,当规模为3的子问题出现的时候,应该是这样:

1__1(11100000)1100

然后第一步将第67个位置上的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 * 2L + 3,中间规模为36的子问题特殊解决,然后大问题的最后一部都是将末尾两个移到前面,因为解决规模为36的子问题需要的步骤分别是36,加上最后一步,需要的操作数刚好是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 }

 

 

 

 

posted @ 2015-04-01 18:16  Lightning34  阅读(531)  评论(0编辑  收藏  举报