#3420_Ronald(罗纳德)----贪心+卡时间

电脑卡了,都不敢写博客了

题目描述

一个国家有n个城市,城市之间连接着双向航空线路。一位疯狂的航空公司总裁Ronald Krump(罗纳德.克朗普)经常改变航班时刻表。更准确地说,他每天都做以下事情:

●选择其中一个城市

●如果该城市和某个其他城市之间之前没有航线那么在这两个城市之间创建一条航线,如果该城市和某个其他城市之间之前已有航线那么取消这条航线

例如,如果从城市5有航线通往城市1和2,但没有航线通往城市3和4,那么在Krump选择城市5并进行操作后,将有从城市5通往城市3和4的航线,但没有从城市5通往城市1和2的航线。

这个国家的公民在想,是否有一天航线图会变为完全图。换言之,当天任意两个不同的城市之间存在一条航线(直达)。写一个程序,根据当前的航线图,确定是否有可能有某一天出现这种情况,通过Krump的操作。

输入格式

第一行输入包含一个整数N(2≤N≤1000)。表示城市的数量。城市从1到N编号。 第二行输入包含一个整数M(0≤M≤N*(N-1)/2)。表示当前航线的数量。 接下来M行每行包含两个不同的整数,表示一条航线连接的两个城市的编号。

输出格式

如果有一天航线图会变为完全图,那么输出“DA”。否则输出“NE”。输出均不含引号。

样例

样例输入1

2
0

样例输出1

DA

样例输入2

3
2
1 2
2 3

样例输出2

NE

解法

这道题老师说有很多解法,像并查集之类的。

先大概看看正解:

简要分析可知,要想得到一个完全图,那么最后一步操作中所选的点,必须是与图中任何一个其他的点没有连接,且其他的点都相互连接的。

但我们没有办法从这个结论入手。

方法一:我们换个方向,从最开始选的点入手。

那么我们考虑强制1号点为最先选的点。

接下来说明这个方法的正确性:

由于两城市之间的航线在选其中一个时都会发生改变,所以,这条航线是否存在取决于Krump的操作次数,故我们可以限定每个城市要么不选,要么只选一次。

那么对于城市1,我们有两种选择:开始不选,开始选一次。那么对于两种选择,我们都可以检查它是否是一个完全图。对于其他任何一个城市,我们都可以通过选它来增加它到1的航线。我们这样就可以贪心地选择所有其他的点,并最后判断是否是一个完全图即可。

方法二:从题意我们分析不可能有重边,因为两个城市间开两条航线浪费空中资源,还有一个结论,若答案是DA,则图中有且仅有两个连通块且这两个连通块是完全的。n个点的完全图边有n*(n-1)/2条边,总边数-数据输入边数,要么等于0(本身就是完全图),要么等于两个完全图各自节点的乘积,我们只需要找到图中一个完全图的节点数,就可以得到结论。

N为1000,暴力枚举每一个点,以这个点为起点,找到一个完全图即可break,并统计出这个完全图的节点数。

↑老师题解上的原话,看不懂是老师的问题~

下面我只讲我的做法:一个小贪心。

思路

先想想,在一个完全图内,边的数量该是n*(n-1)/2,

然而 m≤n*(n-1)/2,它最终得达到n*(n-1)/2才是完全图,

假设我们把一个点的度表示为 d[i],那么有两种情况:

1、d[i]≤n-d[i]-1 ,此时如果对i点做克朗普操作,那么d[i]变为n-d[i]-1,删去了d[i]条边,增加了n-d[i]-1条边,m要么增加,要么不变;

2、d[i]>n-d[i]-1,则m会减少。

所以按照贪心的思路,能使m增加最多的点应优先操作,

那么多次枚举这样的点不就能使m最大了?

所以代码大概就是不断枚举并进行克朗普操作,直到得到完全图就break(补充:当每个点的度都为n-1时则是完全图)

但最多枚举多少遍呢?

考虑到n≤1000,设每个点最多操作32遍(实际不会这么多,但保险起见),总共n*32遍,枚举一次O(n),克朗普操作O(n),

则最多32*1000*2000=64000000,能卡过。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,b[1005];
bool w[1005][1005];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		w[u][v]=w[v][u]=1;
		b[u]++,b[v]++;
	}
	b[0]=n;
	int N=(n<<5);
	while(N--){
		int u=0;
		bool is=1;
		for(int i=1;i<=n;i++)
			if(b[i]<b[u])u=i;//不等式简化一下,相信能看懂
		for(int v=1;v<=n;v++)
			if(v!=u){
				if(w[u][v]){
					w[u][v]=w[v][u]=0;
					b[u]--,b[v]--;
				}
				else{
					w[u][v]=w[v][u]=1;
					b[u]++,b[v]++;
				}
				if(b[v]<n-1)is=0;
			}
		if(b[u]<n-1)is=0;
		if(is)break;
	}
	if(N>=0)puts("DA");
	else puts("NE");
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值