code vs 1106 篝火晚会(置换)

1106 篝火晚会

 

2005年NOIP全国联赛提高组

 时间限制: 1 s
 空间限制: 128000 KB
 题目等级 : 大师 Master
题目描述 Description

佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了小教官。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有n个同学,编号从1n。一开始,同学们按照12……n的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。
佳佳可向同学们下达命令,每一个命令的形式如下:
(b1,b2,...bm-1,bm)
这里m的值是由佳佳决定的,每次命令m的值都可以不同。这个命令的作用是移动编号是b1b2…… bm –1bm的这m个同学的位置。要求b1换到b2的位置上,b2换到b3的位置上,……,要求bm换到b1的位置上。
执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置,那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?

输入描述 Input Description

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

输出描述 Output Description

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

样例输入 Sample Input

4

4

4 3

1 2

1 2

样例输出 Sample Output

2

数据范围及提示 Data Size & Hint


【数据规模】



对于30%的数据,n<=1000

对于全部的数据,n<=50000


题解:初始序列变成目标序列的最小代价就是与目标序列上对应位置的数不同的个数。证明方法:把一个不在应在位置上的数连一条有向边指向它应该去的位置,这样最终会形成环,只要按照环的顺序取(b1, b2,... bm -1, bm),就可以使环中的元素全部归位。 代价就是环中元素的个数。由于可能有多个环,只要位置不对的元素必定是环中的一部分,而环和环肯定不会有公共部分,不然的话某个元素可能要去多个位置,或者某个位置有多个元素要去,所以代价就是不在应在位置上的数的个数。由此可以拓展开:交换次数最少的排序方法是选择排序,最少次数就是sum(环的边数-1)。但是选择排序的时间复杂度是o(n^2),所以无法实现。

对于目标序列,如果我们要全部搜索出来更新答案,肯定不行。因为可以有n个起点,而每个点又有2个方向选择,所以目标序列和初始序列都是2×n的。但是有一个规律:

      起始序列还是固定的,

      任取一个目标序列,对于每一位求一个差值(负数则加上n),

      发现无论如何选择目标序列(同向),目标序列中差值相等的位总是固定的,

     差值的相等的位始终相同是由于原序列是个等差序列,所以在移动时差值要么同时增加要么同时减小,那么一定存在一个序列使这些差值相同的点与原序列的差值同时变为0.

 至于负数注意到当差值为 -k 时它会与 -k+n 同时为 0 ,所以 -k 等价于 -k+n 

      

      所以我们只需要求出一个目标序列,

分别与正向,反向的起始序列做差,统计差值相同位的最大值max,

      答案就是: n-max


#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 500003
using namespace std;
int n,m,ans[N];
int a[N],point[N],v[N],next[N],tot,vis[N],b[N];
int t[N],t1[N],cnt[N];
bool flag;
void add(int x,int y)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void dfs(int x,int num){
	vis[x]=1; ans[num]=x;
	if (num==n){
		flag=true;
		return;
	}
	for (int i=point[x];i;i=next[i])
	if (!vis[v[i]]){
		dfs(v[i],num+1);
		if (flag)  return;
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		int x,y; scanf("%d%d",&x,&y);
		add(i,x); add(i,y);
	}
	flag=false;
	dfs(1,1);
	if (!flag)  {
		printf("-1\n");
		return 0;
	}
	for (int i=1;i<=n;i++)
	 a[i]=i,b[i]=n-i+1;
	int maxn=0;
	for (int i=1;i<=n;i++)
    {
	 t[i]=ans[i]-a[i]; 
	 if (t[i]<0)  t[i]+=n;   
	 cnt[t[i]]++;
    }
    for (int i=0;i<=n;i++)  maxn=max(maxn,cnt[i]);
    memset(cnt,0,sizeof(cnt));
    for (int i=1;i<=n;i++){
     t1[i]=ans[i]-b[i]; 
	 if (t1[i]<0)  t1[i]+=n;
	 cnt[t1[i]]++;
    }
    for (int i=0;i<=n;i++)  maxn=max(maxn,cnt[i]);
    printf("%d\n",n-maxn);
}




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值