【NOIP2005提高组T3】篝火晚会-置换群

(本人本题完成于2016-7-20)

题目大意:有一些点以1,2,3,...,n(3≤n≤50000)的顺序排成一个环,要求将其中一些点进行置换,使得置换后排成的环满足给定的条件:第1个点与第a1,b1个点相邻,第2个点与第a2,b2个点相邻,...,第n个点与第an,bn个点相邻。如果可以满足条件,求出至少要置换多少个点,否则输出-1。

做法:我们可以转换一下思路,要求至少要置换多少个点,可以先求至多可以不置换多少个点。首先按照给定的条件构成一个环,并对每个点的度数进行累加。我们知道,一个环内的节点度数都是2,在累加中,如果发现一个点的度数超过2,则一定无法满足条件。但这里还有一个情况,倘若按照给定条件形成了多个环,依照题目定义也是无法满足条件的,因此要进行特殊判断。构成环后,以标号为1的点为起始点遍历环,可以得到正反两个序列,可以发现这两个序列在经过一定的旋转操作后(别忘了这是一个环)是正好相反的。将这两个序列分别与1,2,3,...,n这个序列做差,如果结果小于0就加上n,就可以得到一个数列r。容易想到,如果r[i]=r[j],就表示经过一定的旋转操作后i和j可以同时落在自己的位置上,于是,我们只要求出最多有多少个r[i]相同,就是至多可以不置换的点数。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,con[50010][2]={0},deg[50010]={0},q[50010]={0},v[50010]={0};
int ans=99999999;

void work()
{
  int x,m=0;
  memset(v,0,sizeof(v));
  for(int i=1;i<=n;i++)
  {
    x=q[i]-i;
	if (x<0) x+=n;
	v[x]++; //v[x]记录r[i]=x的点的数目
	if (v[x]>m) m=v[x];
  }
  if (n-m<ans) ans=n-m;
}

void reverse()
{
  int t;
  for(int i=1;i<n-i+1;i++)
  {
    t=q[i];
	q[i]=q[n-i+1];
	q[n-i+1]=t;
  }
}

int main()
{
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
  {
    scanf("%d %d",&con[i][0],&con[i][1]);
	deg[con[i][0]]++;deg[con[i][1]]++; //累加度数
	if (deg[con[i][0]]>2||deg[con[i][1]]>2||con[i][0]==i||con[i][1]==i) //判断无解情况
	  {printf("-1");return 0;}
  }
  for(int i=0,j=1;;) //求出其中一个序列
  {
    v[j]=1;i++;q[i]=j;
    if (!v[con[j][0]]) j=con[j][0];
	else if (!v[con[j][1]]) j=con[j][1];
	     else if (i!=n) {printf("-1");return 0;} //如果序列内的点数还没达到n,而已经无法再扩展,则判断为有多个环,无解
		      else break;
  }
  work(); //做差
  reverse(); //反转序列
  work(); //再一次做差
  printf("%d",ans);
  
  return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值