如果圆桌骑士有特殊情况(Knights of the Round Table)

题目描述

Being a knight is a very attractive career: searching for the Holy Grail, saving damsels in distress, and drinking with the other knights are fun things to do. Therefore, it is not very surprising that in recent years the kingdom of King Arthur has experienced an unprecedented increase in the number of knights. There are so many knights now, that it is very rare that every Knight of the Round Table can come at the same time to Camelot and sit around the round table; usually only a small group of the knights isthere, while the rest are busy doing heroic deeds around the country. 

Knights can easily get over-excited during discussions-especially after a couple of drinks. After some unfortunate accidents, King Arthur asked the famous wizard Merlin to make sure that in the future no fights break out between the knights. After studying the problem carefully, Merlin realized that the fights can only be prevented if the knights are seated according to the following two rules:

  • The knights should be seated such that two knights who hate each other should not be neighbors at the table. (Merlin has a list that says who hates whom.) The knights are sitting around a roundtable, thus every knight has exactly two neighbors.
  • An odd number of knights should sit around the table. This ensures that if the knights cannot agree on something, then they can settle the issue by voting. (If the number of knights is even, then itcan happen that ``yes" and ``no" have the same number of votes, and the argument goes on.)

Merlin will let the knights sit down only if these two rules are satisfied, otherwise he cancels the meeting. (If only one knight shows up, then the meeting is canceled as well, as one person cannot sit around a table.) Merlin realized that this means that there can be knights who cannot be part of any seating arrangements that respect these rules, and these knights will never be able to sit at the Round Table (one such case is if a knight hates every other knight, but there are many other possible reasons). If a knight cannot sit at the Round Table, then he cannot be a member of the Knights of the Round Table and must be expelled from the order. These knights have to be transferred to a less-prestigious order, such as the Knights of the Square Table, the Knights of the Octagonal Table, or the Knights of the Banana-Shaped Table. To help Merlin, you have to write a program that will determine the number of knights that must be expelled. 

国王有时会在圆桌上召开骑士会议。

由于骑士的数量很多,所以每个骑士都前来参与会议的情况非常少见。

通常只会有一部分骑士前来参与会议,而其余的骑士则忙着在全国各地做英勇事迹。

骑士们都争强好胜,好勇斗狠,经常在会议中大打出手,影响会议的正常进行。

现在已知有若干对骑士之间互相憎恨。

为了会议能够顺利的召开,每次开会都必须满足如下要求:

  1. 相互憎恨的两个骑士不能坐在相邻的两个位置。
  2. 为了让投票表决议题时都能有结果(不平票),出席会议的骑士数必须是奇数。
  3. 参与会议的骑士数量不能只有 11 名。

如果前来参加会议的骑士,不能同时满足以上三个要求,会议会被取消。

如果有某个骑士无法出席任何会议,则国王会为了世界和平把他踢出骑士团。

现在给定骑士总数 nn,以及 mm 对相互憎恨的关系,求至少要踢掉多少个骑士。

输入

The input contains several blocks of test cases. Each case begins with a line containing two integers 1 ≤ n ≤ 1000 and 1 ≤ m ≤ 1000000 . The number n is the number of knights. The next m lines describe which knight hates which knight. Each of these m lines contains two integers k1 and k2 , which means that knight number k1 and knight number k2 hate each other (the numbers k1 and k2 are between 1 and n ). 
 

The input is terminated by a block with n = m = 0 . 

输入包含多组测试用例。

对于每个用例,第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 a 和 b,表示骑士 a 和骑士 b 相互憎恨。

当遇到某行为 0 0 时表示输入终止。

输出

For each test case you have to output a single integer on a separate line: the number of knights that have to be expelled. 

每个测试用例输出一个整数,表示结果。

每个结果占一行。

样例输入

5 5
1 4
1 5
2 5
3 4
4 5
0 0

样例输出

2

提示

n≤1000,m≤10^6

特殊条件:

过程中不会出现某一位骑士的非敌人数大于1但是却无法参与任何会议。

特解(一分没有):

        首先根据题意,相互憎恨的骑士不能坐在相邻两个位置,又因为出席的骑士数为奇数,因此就意味着如果与一个骑士不相互憎恨的人数小于等于1,那么这个骑士就会被踢掉。现在来考虑踢掉这个骑士会产生什么效果。

  首先,憎恨他的人不会因此而多一个朋友,所有剩下的骑士中该踢的还是会踢。其次,原本所有与这个骑士可以做朋友的人都会少一个朋友,即他们有可能会被踢掉。总的来说,对于一个能踢的骑士,无论怎样踢其他人都无法使得他不会被踢,所以该踢的骑士必须踢。乍一听,这是道模拟?咋可能,m的数据都10的6次方了,并且还是多组数据,因此每次O(N)扫描不现实。

  可以尝试用一个堆,记录与某个骑士做朋友的人有多少,每次取最少的,然后用邻接表的方式找到他的朋友,并且让他朋友的朋友数减一。现在再来看一看效率,首先建立一个堆是O(nlogn),然后要让朋友的朋友数在堆中得到改变,又是一个O(nlogn),一个人的极限朋友是1个,因此总效率(n*nlogn),应该是比较优的效率了。至于为什么改变朋友的朋友总数是O(nlogn),因为可能出现如下极端情况,一个人原本朋友很多,结果那些朋友都很快就被踢了,最后他也要被踢,但是pop却一直没法找到他(优先队列),就可能出现答案错误。由此我们再来想一个方法,不用堆。

  能否用一个朴素的数组来解决这道题呢?显然是可以的。首先把数组按照朋友数升序排列,然后从朋友数最少的开刀。由于一个骑士被踢了,会最多导致另一个骑士可能被踢,因此只需要记录那个骑士还剩下多少个朋友(不需要准确的编号),直到朋友数小于等于1了(后面的应该是只可能等于1 啊,但是对于前面的来说也不排除等于0的可能),就直接对这个人进行操作了。由此每一次踢人是O(n)的,找到朋友总体平均下来是O(n)的,统计答案也是O(n)的。唯一让人有些头疼的是记录朋友数……直接记录敌人数,然后总人数减去敌人数再减1不就是朋友数了吗?至于快速找到唯一的朋友,可以考虑用二维数组vis[1001][1001]来直接记录,同时用一个链表来实时更改朋友链接(即快速由一个朋友转移到另一个朋友),可以桶排序后O(n*n)来解决。就可以以整体效率O(n*n*t)来完成本题。

错误原因

        很简单的一个,一个骑士有且仅有有个非敌人的人,但是这两个人相互敌视,因此这个骑士还是会被踢掉。

可能的改进

        对于每一个骑士,再存一个并查集,相当于找环(很接近正解中判奇环),环中的人表示相互不仇视,那么就相当于把num换成最大size的环,其他的条件不变。但是由于是n三次方的,所以也没有选择写(写了可能会有点分)。

代码

有兴趣可以看看

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{ int val,num; }a[1001];
int n,m,vis[1001][1001],seg[1001],rt[1001];
int nxt[1001][1001],pre[1001][1001],getr[1001];
//链表需要 
bool comp1(node p,node q) 
{ return p.val<q.val; }
int main()
{
	while(1)
	{
		memset(rt,0,sizeof(rt));//是否需要踢出
		memset(vis,0,sizeof(vis));//是否仇视
		memset(vis,0,sizeof(nxt));//后
		memset(vis,0,sizeof(pre));//前
		memset(vis,0,sizeof(seg));//编号转排名
		memset(vis,0,sizeof(getr));//编号最小的朋友
		scanf("%d%d",&n,&m);
		if(m+n==0) break;
		for(int i=1;i<=m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			vis[a][b]=vis[b][a]=1;
		}
		for(int i=1;i<=n;i++)
		{
			int pret=0,t_nus=0;
			for(int j=1;j<=n;j++)
			{
				if(!vis[i][j]&&i!=j)
				{
					t_nus++;
					nxt[i][pret]=j;
					pre[i][j]=pret;
					if(pret==0) getr[i]=j;
					pret=j;
				}
			}
			a[i].num=i;a[i].val=t_nus;
		}
		int ans=0;
		sort(a+1,a+n+1,comp1);
		for(int i=1;i<=n;i++) 
		seg[a[i].num]=i;
		for(int i=1;i<=n;i++)
		{
			if(a[i].val>1) break;
			rt[i]=1;
			if(a[i].val==0) continue;//追不动了(没人)
			int x=i;
			while(a[x].val==1)//连锁追踪
			{
				rt[x]=1;
				int tr=seg[getr[a[x].num]];
				
				a[tr].val--;
				
				int nus1=a[tr].num;
				int nus2=a[x].num;
				if(a[tr].val==0) break;
				nxt[nus1][pre[nus1][nus2]]=nxt[nus1][nus2];
				if(!pre[nus1][nxt[nus1][nus2]])
				pre[nus1][nxt[nus1][nus2]]=pre[nus1][nus2];
				if(pre[nus1][nus2]==0) getr[nus1]=nxt[nus1][nus2];
				x=tr;
			}
			a[i].val--;
		}
		for(int i=1;i<=n;i++)
		if(rt[i]) ans++;
		printf("%d\n",ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值