【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 }

 

 

 

 

转载于:https://www.cnblogs.com/lightning34/p/4384537.html

### 关于洛谷平台上线段树练习题目的推荐 #### 题目一:P3810 【模板】线段树 1 此题目作为入门级的线段树构建与基本操作实现,适合初学者掌握线段树的基础概念和简单应用。通过这道题可以熟悉如何在线段树上执行单点更新以及区间求和的操作[^2]。 ```cpp #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 5; struct SegmentTree { int sum[maxn << 2]; void pushUp(int rt) {sum[rt] = sum[rt<<1] + sum[rt<<1|1];} void build(int l, int r, int rt){ if(l == r){ scanf("%d", &sum[rt]); return ; } int m = (l+r)>>1; build(lson); build(rson); pushUp(rt); } // Other functions like update and query can be implemented here. }; int main(){ SegmentTree tree; int n,m,opt,x,y,k; char str[2]; while(scanf("%d%d",&n,&m)!=EOF){ memset(tree.sum,0,sizeof(tree.sum)); tree.build(1,n,1); for(int i=0;i<m;++i){ getchar(); gets(str); sscanf(str,"%d %d %d",&opt,&x,&y); switch(opt){ case 1 : k=y-tree.a[x]; tree.update(x,k,1,n,1);break; case 2 : printf("%lld\n",tree.query(x,y,1,n,1)); break; } } } return 0; } ``` #### 题目二:P2791 幼儿园篮球题 该问题不仅考察了对线段树的理解程度,还涉及到更复杂的逻辑思考能力。在这个场景下,需要利用线段树来高效解决有关子树内节点权重总和的问题,在实际编程竞赛中具有较高的实用性价值[^1]。 #### 题目三:P4566 [FJOI2018]新型城市化 这是一个较为高级的应用实例,涉及动态开点线段树的知识点。对于想要深入研究数据结构优化技巧的学习者来说是一个很好的挑战对象。这类题目有助于提高解决问题的能力,并加深对复杂算法设计模式的认识。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值