原题

佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有n个同学,编号从1到n。一开始,同学们按照1,2,…,n的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。佳佳可向同学们下达命令,每一个命令的形式如下:

(b1, b2,... bm -1, bm)

这里m的值是由佳佳决定的,每次命令m的值都可以不同。这个命令的作用是移动编号是b1,b2,… bm –1,bm的这m个同学的位置。要求b1换到b2的位置上,b2换到b3的位置上,…,要求bm换到b1的位置上。

执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置,那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?

【输入文件】

输入文件fire.in的第一行是一个整数n(3 <= n <= 50000),表示一共有n个同学。其后n行每行包括两个不同的正整数,以一个空格隔开,分别表示编号是1的同学最希望相邻的两个同学的编号,编号是2的同学最希望相邻的两个同学的编号,…,编号是n的同学最希望相邻的两个同学的编号。

【输出文件】

输出文件fire.out包括一行,这一行只包含一个整数,为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望,则输出-1。

【样例输入】
4
3 4
4 3
1 2
1 2

【样例输出】
2

【数据规模】
对于30%的数据,n <= 1000;
对于全部的数据,n <= 50000。

题意简述

有n(n <= 50000)个同学坐在一起,围成一圈(1号同学和n号同学相邻)。现在要给他们重新安排座位,让每个人都能和自己指定的两个喜欢的同学坐在一起。

重新安排座位要通过下达换座位指令完成,一个换座位指令可以指定任意一些同学,比如b1、b2、b3、...、bm(m也是任意指定的),该指令会移动这m个同学的位置,让b1坐在b2位置上,b2坐在b3位置上,bm坐在b1位置上。这样一个指令的复杂度是m。

要求是在满足每个人的意愿的前提下,给出最优的换座位方案(指令复杂度总和最小),输出复杂度。如果无法满足所有人意愿,输出-1。

分析

1、首先要解决的问题是:找到能满足所有人要求的座位顺序。

猜测,满足要求的顺序可能有非常多个(比如阶乘级别),也可能只有唯一解(这样的话只要找出最优换座位方案就行了)。

找到一个可行解比较简单,不妨把1号同学放在1号座位,然后从这个同学开始,挑他意愿相邻的同学中还没有坐下的同学安排到下一个座位,这样依次安排就可以了。(安排1号同学时,在1号同学的相邻同学中任选一个,之后安排其他同学时都只有一种选择)

这样的可行解有多少个呢? 一种思考方式是考虑生成可行解时的可能性有多少种。注意刚才我们把1号同学放在了1号座位,然后从他喜欢的两个同学中随机挑了一个放在2号座位。这里涉及到两次选择,第一次选择有n种可能,第二次选择有2种可能,因此可能性共有n×2种。选择了这两个之后,剩下每个同学的位置都能固定下来了,所以总共有n×2个可行解

从另一个角度考虑也可以得到n×2的答案,就是对第一个可行解序列进行变换,只要保持相对关系不变,依然是可行解。可以做的变换有两种,一是考虑到是这些人围坐成一个圈,可以将这些同学都平移到下一个座位上(最后一个同学坐到1号座位上),相对关系不变,二是考虑到不区分左右,哪怕将整个序列颠倒过来,相邻关系也不变。这两种变换创造出n×2种可行解,结论一致。

至于无法满足要求的情况,一种是存在一个同学有3个同学想跟他做邻居,也就是左拥右抱都抱不过来的情况,这样显然是无法满足的,另一种是一部分同学形成一个环,剩下的同学又形成一个环,这样违背了题目的要求,也要输出-1。当然,只要以找可行解的思路算一遍,出错了就报-1,也是没问题的。

2、接下来的问题是,对于某个可行的目标座位顺序,怎么设计最优的换座位指令?

  • 最初的座位顺序为“原序列”,即1、2、3、。。。、n,
  • 目标的座位顺序为“目标序列”,如4、3、2、。。。、1,
  • 求解的是从原序列变换到目标序列的最小代价。

先举些例子,边看边思考:

  1. 1、2、3,变换到1、3、2,只需发出一条(2,3)指令,代价为2.
  2. 1、2、3,变换到3、1、2,只需发出一条(2,3,1)指令,代价为3.
  3. 1、2、3、4,变换到2、1、4、3,最少需发出两条指令,(1,2)、(3、4),代价为4。
  4. 1、2、3、4、5,变换到2、1、3、5、4,最少代价为4.
  5. 1、2、3、4、5,变换到1、3、4、5、2,一种方案为(2,3,4)、(2,5),代价为5,另一种方案为(2,5,4,3),代价仅为4(最少)。

可以摸索到一个规律,两个序列中相同的部分无需调动,调动只会带来额外的代价;差异部分则可分成若干个组,以组与组之间没有共同数字为划分依据,如例2中有(1,2,3)一个组,例4中有(1,2)和(4、5)两个组。可以想象并证明,最优方案中每条指令只会操作一个组,不会出现涉及多个组的指令。

那问题是一个差异组需要多少代价的指令才能完成变换?答案是该组的长度。 可以做到仅一条指令完成。证法是先证所需代价不可能小于长度(小于长度的指令集合无法包括所有的差异数字,自然无法完成变换),再证一定可以找到同长度的置换序列,形成指令,从而完成变换。

这样来看,最优方案是对每个组各执行一次指令,而相应的总代价是各差异组长度之和,其实就是原序列和目标序列的差异位置数。 (因为题目不要求我们输出具体指令内容,我们只要关注指令代价就有多少可以了)

3、最后的大难点是TLE优化:满足要求的目标座位顺序有2n个,计算一个变换最小代价的复杂度为n,时间总复杂度是n2,但n最大是5万,必定要超时,如何优化?

考虑这些目标座位顺序是很相似的,在计算它们的变换代价过程中可能存在冗余,一般来说,这是优化的下手点。

一共有3种可行思路,最后能得到同样的结论。

先设原序列为A(1、2、3、。。。、n),第一个可行解的序列为B(如4、5、6、1、2、3、。。。),则最优代价公式为n-|{1, A[i]=B[i]}|。假设只考虑由平移产生的可行解,并设平移一轮为整体向右移动1个单位,则第k轮平移的最优代价公式为n-|{1, i==B[i+k mod n] 0<=k<n}|,也就是在算平移n个单位后,新序列有多少个位置与原序列重合。所有轮次的最优代价为n-max{|{1, i=B[i+k mod n]}|, 0<=k<n}。

思路a:感性理解

虽然看起来每一轮新序列都会发生变化,必须把n个位置都重新检查一遍看是否数字一样,但换个角度来看,对每一个位置而言,都只在某一轮才会发生数字吻合的情况,不妨称为幸运轮次。因此每轮检查的过程,都只需看每个位置的幸运轮次是否是当前轮次,一致的就是1,不一致的就是0。更幸运的是,每个位置的幸运轮次可以预先计算好。想想便知,只要算出了幸运轮次的数据,直接就可以算出k轮的最优代价,优化成功。

思路b:数学公式优化

把最优代价公式改写为n-max{|{1, i+k mod n=B[i]}|, 0<=k<n},它又等价于n-max{|{1, k==(B[i]-i) mod n}|, 0<=k<n},这样只需要预处理(B[i]-i) mod n,就知道k取每个值时公式结果是多少了。(注意负数的取模运算规则,-1 mod 5=4)

思路c:动态规划+数学公式优化

要使平移后的新序列能与原序列吻合,考虑到原序列是递增数列的特性,新序列中也需要存在递增的子结构,且要尽可能的长,比如。。、3、4、5、X、X、8、X、10、。。其中X代表任意数字。这里的3、4、5、8、10是一定可以在某次平移时跟原序列对的上的,X无所谓是多少,但注意X的个数要能刚好填补前后数字的差距,才能在平移后跟原序列对的上。那现在的问题就是,如何找到这样的最长递增子序列?

这可以用动态规划解决。动态规划的状态设置为f[i]表示以B[i]为递增子序列的结尾,能构成的最长的递增序列的长度,容易写出动态转移方程,f[i]=max{f[i-k]+1, B[i-k]=B[i]-k 0<k<n}。但该方程仍然不容易求解,需要将该方程改写为f[i]=max{f[j]+1, A[i]-A[j]=B[i]-B[j] j<i},再整理为f[i]=max{f[j]+1, B[j]-j=B[i]-i j<i},这样只需预处理B[x]-x就能快速算出f了。如果再用心看的话,可以看出f[i]=|{1, B[j]-j=B[i]-i}|。

找到递增子序列离答案只有一步之遥,剩下的问题是整个是个环,最长递增子序列的开端不一定在f[i]之前,可能在f[i]之后,如5、6、7、1、2、3、4这个序列。简单的解决方法是预先将B扩展一倍到n×2长度,将原来的n个数字再复制一次,再求解即可。


上述几种思路都可以将算法复杂度优化到O(n),且有异曲同工之妙。

至于上述讨论没有提到翻转可行解的情况,处理方式也很简单,只要翻转过来再算一次取最优解就好了。

编程实现

#include <stdio.h>
#include <string.h>int n;
int neighbour[50001][2];
int B[50001];
bool seated[50001];
int k[50001];int main()
{FILE *in = fopen("fire.in", "rt");FILE *out = fopen("fire.out", "wt");int ans = -2;fscanf(in,"%d",&n);for (int i=1;i<=n;i++){fscanf(in,"%d%d",&neighbour[i][0],&neighbour[i][1]);seated[i] = false;}bool impossible = false;B[1] = 1;seated[1] = true;int last_seated = 1;int seated_count = 1;while (seated_count < n && !impossible){int current;if (seated[neighbour[last_seated][0]] && !seated[neighbour[last_seated][1]]){current = neighbour[last_seated][1];} else if (!seated[neighbour[last_seated][0]] && seated[neighbour[last_seated][1]]){current = neighbour[last_seated][0];} else if (last_seated == 1 && seated_count == 1){// choose either neighbour of the 1st personcurrent = neighbour[1][0];} else {impossible = true;break;}// verifyif (neighbour[current][0] == last_seated || neighbour[current][1] == last_seated){B[++seated_count] = current;seated[current] = true;// verifyif (seated_count == n && (neighbour[1][1] != current || (neighbour[current][0] != 1 && neighbour[current][1] != 1))){impossible = true;break;}} else {impossible = true;break;}printf("%d,", current);last_seated = current;}if (impossible || seated_count < n){ans = -1;} else{memset(k, 0, sizeof(k));for (int i=1;i<=n;i++){++k[((B[i]-i)+n)%n];}for (int i=0;i<n;i++){if (n-k[i]<ans || ans<0) ans=n-k[i];}// reversememset(k, 0, sizeof(k));for (int i=1,j=n;i<j;i++,j--){int t;t=B[i];B[i]=B[j];B[j]=t;}// againfor (int i=1;i<=n;i++){++k[((B[i]-i)+n)%n];}for (int i=0;i<n;i++){if (n-k[i]<ans || ans<0) ans=n-k[i];}}fprintf(out, "%d\n", ans);printf("\n%d\n", ans);fclose(in);fclose(out);return 0;
}

[原创][NOIP2005]篝火晚会(超详细题解,3种思路)相关推荐

  1. noip2005篝火晚会 2008.10.18

    noip2005篝火晚会 2008.10.18 注意: 1.       刚开始,我用的是图来标记这两个人是否已经输出过,一直出错,后来看了标称,方法很好 2.       求出序列后,因为是圆环,要 ...

  2. [noip2005]篝火晚会

    佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官".在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会.一共有n个同学,编号从1到n.一开始, ...

  3. NOIP2005 篝火晚会 解题报告

    佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官".在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会.一共有n个同学,编号从1到n.一开始, ...

  4. 信息学奥赛一本通超详细题解,动画图文题解

    内容来源于微信公众号:大神编程.已经过原文作者授权. 更新时间:2020-11-5 现在开始更新基础算法题. 个人感言:从未见过如此详细的题解,动画.图文结合,适合任何水平的选手.尤其是特别适合自学的 ...

  5. 河工计院ACM2022寒假培训题单以及超详细题解

    目录 货仓选址 校门外的树 奖学金 蛇形矩阵 找硬币 回文平方 品种邻近 平方矩阵 II 十三号星期五 阶乘 干草堆 火星人 整数集合划分 最大的和 剪绳子 分巧克力 a^b 数独检查 ISBN号码 ...

  6. 2021卓见杯第三届CCPC河南省省赛所有题超详细题解附加榜单真题解析,简单代码+详细注释+思想,要看的,补题的速速点进来 2021 10.30

    本人现在比较菜,所以难免出现错误,文章中有不太恰当地方,还请大家指正. 是否因为出题人的简短题解而发愁?,是否看不懂出题人的变态模板标程?是否因为自己是小白而苦恼?来看这片文章,帮助你解决这些问题 题 ...

  7. PAT乙级全套超详细题解【建议收藏】

    PAT OJ地址 代码 上面专栏就是PAT乙级共95道题的所有代码,在专栏里已经按照顺序排好序了. 专栏地址 讲解 PAT代码那里有的个别有题解,但是文字很难把一道题讲懂. 于是,为了方便更好的理解. ...

  8. PTA天梯赛L1刷题总结(三)15分题型(超详细题解)

    多么感人!时隔一年多,我终于来更新15分题型的博文了.突然发现L1的题目量扩充了!一共有哦20道题.哎~都写一遍题解好了.在这里推荐下胡凡的算法笔记!在基础算法和数据结构上给了我很多细致的讲解启发.过 ...

  9. Luogu1053 NOIP2005篝火晚会

    首先造出所要求的得到的环.如果将位置一一对应上,答案就是不在所要求位置的人数.因为显然这是个下界,并且脑补一下能构造出方案达到这个下界. 剩下的问题是找到一种对应方案使错位数最少.可以暴力旋转这个环, ...

最新文章

  1. 2017.4.18 静态代码分析工具sonarqube+sonar-runner的安装配置及使用
  2. Scrollbar中滚动条的设置
  3. Golang环境配置以及GOPATH与gomod的关系
  4. Orchard Core一分钟搭建ASP.NET Core CMS
  5. 苹果Mac AI 智能图像降噪工具:Topaz DeNoise AI
  6. C#中如何调用动态链接库DLL
  7. margin-left:10px; 不同浏览器距离为什么不一样?
  8. C#序列化出现“因其保护级别而不可访问。只能处理公共类型。”
  9. 施一公:如何写好一篇学术论文?
  10. 一文了解Markdown语法
  11. 计算机内存条如何区分频率,怎么看内存条频率,详细教您怎么看内存条频率
  12. 视频会议室需要什么设备 远程视频会议设备清单
  13. dbms_aw.eval_number
  14. 仿佛来自虚空,Grothendieck的故事2
  15. 利用tkinter设计贷款计算器
  16. 四足爬行机器人运动_四足爬行机器人控制研究
  17. 手把手教你学财报01
  18. MdEditor-v3中上传照片的前后端对接(图片上传至又拍云云储存)
  19. c语言字符串碱基互补配对,碱基互补配对原则
  20. secscan-authcheck(越权漏洞检测工具) 安装总结

热门文章

  1. java科大讯飞语音合成,亲测
  2. “../28004x_generic_ram_lnk.cmd“,遇到RAM内存不够,需重新配置。
  3. 篮球图片html页面代码,教你用PS制作一个非常逼真的篮球图片
  4. java调用kettle自定义kettle.properties配置文件路径
  5. json数据条件查询,json数据sql查询中文乱码
  6. 苹果授权登录绑定手机号被拒绝
  7. 语音识别引擎接口 将声音转换为文字显示
  8. python运算符讲解
  9. PHP require、include、require_once、include_once用法及区别
  10. HDU 2121 Ice_cream’s world II (最小树形图+虚根)