【原题】【noip2005 T3】【基础算法】篝火晚会

问题

【问题描述】

佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有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。

 

分析

每个人只可能出现两次,所以无解的情况很容易判断。题目虽然要我们求一个圈,但显然我们可以从1号开始求一条链,最后再判断是不是一个圈。求一条链的只需要O(n)的时间。还需要判断一下是否每个人都正好出现一次。求出的圈当然有两个方向,这一点也至关重要。
怎么求m呢?m实际上就是不在位的人数总和,如果不能发现这点,就是没有完全理解本题的含义。(在位表示人的编号和他所在的位置一样)
先看一个例子:
如果n=7,求出的链是:1 7 2 3 6 5 4,如果保持1不变,那么变回1 2 3 4 5 6 7序列(下标)需要的操作为6,因为只有1号在位。但是,如果把圈旋转一格,变成7 2 3 6 5 4 1,那么操作只需要4次,2、3和5都在位。所以,我们要把圈进行旋转,使得数组中下标和值相等的数最多,记为p。那么答案m=n-p。
可以开一个数组,记录每个数需要向左(向右也可以)移几次能够移回他应该去的位置(下标和他现在所在位置的距离i),给相应的pi加一。将数组反向,再做一遍,求出qi。从两次的pi和qi中取最大值,那么m=n-max{pi, qi}。实际上这里是在对每个数进行分类,按照归位的距离分类,同一类的同时归位。问题问得其实就是何时归位的元素最多。(摘自百度空间)
2.至于为什么正着反着都求一次,我也不太挺明白,好像是因为你所求的序列是无向的,
所以正反方向都要求一次。
3.还有重要的一点,求正确序列时不要用递归,循环就好,否则会栈溢出。(以后做类似的题要注意,虽然递归很好理解,但数据太大的话很容易栈溢出,可以用循环)。

( 以上摘自tyvj)我比较懒……

反思

这个题一看根本想不到什么算法,也许出题者根本不是在考察算法,而是在考察考生的基本素养。发现问题的本质,而且模拟的技巧也很重要,比如构造目标序列,判断矛盾等,很有技巧性。

code

program liukee;
var
  wish:array[0..50000,1..2] of longint;
  a:array[0..50000] of longint;
  d,w:array[0..50000] of longint;
  n,i,j,ans,temp:longint;
begin
  readln(n);
  for i:=1 to n do
  begin
    readln(wish[i,1],wish[i,2]);
	inc(d[wish[i,1]]);
	inc(d[wish[i,2]]);
  end;
//-------读入
  for i:=1 to n do
  begin
    if d[i]<>2 then
	begin
	  writeln(-1);
	  halt;
	end;
  end;//同学们的愿望存在相互矛盾
  a[1]:=1;
  a[2]:=wish[1,1];
  for i:=3 to n do
  begin
    if a[i-2]=wish[a[i-1],1] then a[i]:=wish[a[i-1],2]
	                      else a[i]:=wish[a[i-1],1];
  end;//构造目标序列a
  if a[n]<>wish[1,2] then
  begin
    writeln(-1);
	halt;
  end;//判断矛盾
  ans:=0;
  for i:=1 to n do
    inc(w[(a[i]-i+n) mod n]);
  for i:=0 to n-1 do
    if w[i]>ans then ans:=w[i];//第一次统计
  fillchar(w,sizeof(w),0);
  for i:=1 to (n+1)>>1 do
  begin
    temp:=a[i];
	a[i]:=a[n-i+1];//将序列倒置
	a[n-i+1]:=temp;
  end;
  for i:=1 to n do
    inc(w[(a[i]-i+n) mod n]);
  for i:=0 to n-1 do
    if w[i]>ans then ans:=w[i];
  writeln(n-ans);//答案
end.

	


posted @ 2010-11-12 18:38  liukee  阅读(1120)  评论(0编辑  收藏  举报