NOIP2005篝火晚会题解
佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有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。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?
输入第一行是一个整数n(3<=n<=50000),表示一共有n个同学。其后n行每行包括两个不同的正整数,以一个空格隔开,分别表示编号是1的同学最希望相邻的两个同学的编号,编号是2的同学最希望相邻的两个同学的编号,……,编号是n的同学最希望相邻的两个同学的编号。
这一行只包含一个整数,为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望,则输出-1。
4
3 4
4 3
1 2
1 2
2
【数据规模】
对于30%的数据,n<=1000;
对于全部的数据,n<=50000。
30%
第一个部分数据是解决整道题的开始,很多分析部分数据时得到的结论会一直贯穿下去,一直到设计出完美的算法,所以每一个部分数据都是很重要的。
题目中所提到的命令可以概括的说为,任意选择(即可以无序,且不必相邻)m个人,让他们交换位置,且交换位置的过程是一个环,比如a->b->c->d->a,这样的代价为4,明显地 ,当一个人到达了他想在的位置后,就永远不需要再挪动了,而且每个人到达它想在的位置的代价必定为1,因此,当目标序列确定时,不在位人数即为这种情况下的解。所以就需要先构造一个初始目标环,每次让所有元素向右旋转(对应到数组就是所有元素右移),N次下来,统计每次的代价,取最小值即为答案。需要关注的是,由于题目没指明环的方向,所以逆向也要做一遍。时间复杂度O(N^2)
对于无解的判断:明显地,如果目标环存在,则一定有解。否则无解,我的方法是进行一次遍历,如果能够正好经过N个点,则有解,否则无解。
100%
以下要说的方法是我在一篇博文中看到的。30%的方法中,我把每种情况都枚举,这是没必要的。在目标环中,如果一些元素的相对位置与原环中的相同,我就可以让两个环中的这些元素对齐,此时若这些元素的个数为k,则代价m=N-k。明显地,找出最长的一组就可以让代价最小。
要找最长的相对位置相同的串,也就是找最长的递增且权值之差等于序号之差的序列,DP可行,加入优化可以到O(nlogn)。还有一种简单的方法,如果a和b的相对位置与序号的相对位置和c和d的相对位置与序号的相对位置相同,并无法确定a、b、c、d相对位置是否都与序号的相对位置相同。但是我如果知道a与其序号位置、b与其序号位置、c与其序号位置、d与其序号位置的差值都相等,就一定有a、b、c、d与序号的相对位置相等。因此用a[i]存储目标环中权值与序号差值为i的数的个数,最后找出最大值k,N-k即为解。同样要反向做一次。时空复杂度皆为O(N)。
代码:
<span style="font-family:Arial;">//NOIP2005提高组 篝火晚会 神奇算法
#include <cstdio>
#include <algorithm>
#define maxn 50000
#define inf 0x3f3f3f3f
using namespace std;
int cir[maxn], N, want[maxn][3], start, a[maxn], b[maxn];
void input()
{
int i, a, b;
scanf("%d",&N);
for(i=0;i<N;i++)
{
scanf("%d%d",&a,&b);
a--, b--;
want[i][1]=a, want[i][2]=b;
}
}
bool check()
{
int i, cnt, a, b;
bool flag[maxn]={0};
i=0;
flag[0]=true;
cnt=1;
while(true)
{
a=want[i][1];
b=want[i][2];
if(!flag[a])
{
flag[a]=true;
cnt++;
i=a;
}
else if(!flag[b])
{
flag[b]=true;
cnt++;
i=b;
}
else break;
}
if(cnt==N)return true;
else return false;
}
int work()
{
int ans=inf, i, t;
if(!check())return -1;
cir[0]=0;
cir[1]=want[0][1];
for(i=1;i<N-1;i++)
if(cir[i-1]==want[cir[i]][1])
cir[i+1]=want[cir[i]][2];
else cir[i+1]=want[cir[i]][1];
for(i=0;i<N;i++)
{
a[(i-cir[i]+N)%N]++;
b[(N-1-i-cir[i]+N)%N]++;
}
t=-inf;
for(i=0;i<N;i++)
t=max(t, max(a[i],b[i]) );
return N-t;
}
int main()
{
input();
printf("%d\n",work());
return 0;
}</span>