周日测试部分题解

H 大碗宽面

题目大意

有 n 个人,其中 q 对人是敌人,p 对人是朋友,其余每对人均是陌生人。

  • 朋友之间会握手一次,
  • 敌人之间不会握手,
  • 对一对陌生人而言,如果其中一个人的朋友之一是另一个人的敌人,则不握手,否则握手。

求一共握了几次手。

算法分析

1,暴力枚举(三层循环会超时)

记录每对  u,v  记录他们握手的次数ans

  • 如果 u,v 是朋友,则他们握手,次数 ans++
  • 如果 u,v 是敌人,则他们不握手,次数 ans 不变
  • 如果 u,v 是陌生人,令 w 从 1 到 n 枚举,如果存在一个 w 使得 w 是 u 的朋友且是 v 的敌人(或是 v 的朋友且是 u 的敌人),则符合陌生人之间不握手的条件,ans 不变,否则 ans++,输出答案。

可以用两个二维数组 isFriend[][] , isEnemy[][] 来判断是不是敌人或朋友

eg:4个人

1,2 和 1,3 和 1,4 和 2,3 和 2,4 和 3,4共比较 6 次 (1,2比较和2,1比较一样只算一次)

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 3e4 + 10;

int n, p, q;
bool isEnemy[N][N], isFriend[N][N];//记录是否是敌人或朋友 

int main()
{
	cin >> n >> p >> q;
	for(int u, v, i = 1; i <= p; i++)
	{
		cin >> u >> v;//记录u和v(v和u也)是朋友 
		isFriend[u][v] = isFriend[v][u] = true;
	}
	for(int u, v, i = 1; i <= q; i++)
	{
		cin >> u >> v;//记录u和v(v和u也)是敌人 
		isEnemy[u][v] = isEnemy[v][u] = true;
	}
	int ans = 0;//记录握手次数 
	for(int u = 1; u <= n; u++)//u从1开始依次与v相比 
	{
		for(int v = u + 1; v <= n; v++)//1,2和2,1只算一次,所以直接让v从u+1开始遍历不重复 
		{
			if(isFriend[u][v])//u和v是朋友,次数+1 
			{
				ans++;
			}
			else if(!isEnemy[u][v])//u和v是陌生人 
			{
				bool flag = false;//记录是否找到了符合陌生人不握手的条件 
				for(int w = 1; w <= n; w++)
				{
					if((isFriend[u][w] && isEnemy[w][v]) || (isFriend[v][w] && isEnemy[w][u]))
					{//如果u和w是朋友,w和v是敌人,或者v和w是朋友,w和u是敌人,不握手 
						flag = true;//记录下来 
						break;
					}
				}
				if(flag == false) ans++;//flag没变证明没找到,握手 
			}
		}
	}//三层for循环,(n3) 计算机1s可以处理大约十的八次方的运算 当n=1e4会超时 
	cout << ans << endl;
	return 0;
}

2,优化

陌生人太多了,几乎都会握手,那可以找出不握手的条件,用握手的总次数减去不握手的次数,就是最终握手次数。

每个人都会和n-1个人握手,所以一共是n*(n-1),不过会重复握手一次,所以总共握手次数是n*(n-1)/ 2;p队朋友握手,已经算过了,q队敌人不握手,所以此时握手次数是n*(n-1) / 2 - q;

现在算陌生人情况:u和v是敌人且v和w是朋友,此时不握手,所以此时先判断u和v是敌人关系,再判断v和w是朋友关系,如果u和w是朋友关系,ans不变化,如果u和w是敌人关系,因为之前已经减去过所有敌人关系了,所以ans不变化,如果u和w是陌生人关系,那么此时ans--;同样判断了u还要再判断v(判断v和w的关系如上图),同样ans--;(不过在算陌生人关系时,u和v是敌人,v和w是朋友,u和w是陌生人,ans--,与u和y是敌人,y和w是朋友,u和w是陌生人ans--,算重复了,所以此时我们需要记录这对关系u和w让他们变成敌人关系就不需要重复减了)最终代码如下

#include<iostream>
#include<algorithm>

using namespace std;

const int maxn = 30005;

int n, p, q;
bool isEnemy[maxn][maxn], isFriend[maxn][maxn];
int enemy[2][maxn];//记录每对敌人关系 
 
int main()
{
	cin >> n >> p >> q;
	for(int u, v, i = 1; i <= p; i++)
	{
		cin >> u >> v;
		isFriend[u][v] = isFriend[v][u] = true;
	}
	for(int u, v, i = 1; i <= q; i++)
	{
		cin >> u >> v;
		isEnemy[u][v] = isEnemy[v][u] = true;
		enemy[0][i] = u;//记录每对敌人关系的第一个数 
		enemy[1][i] = v;//记录每对敌人关系的第二个数 
	}
	int ans = n * (n - 1) / 2 - q;//不算陌生人握手情况的握手总数 
	for(int i = 1; i <= q; i++)
	{
		int u = enemy[0][i], v = enemy[1][i];//u是第i队敌人第0个数,v是第i队敌人第一个数 ,u v是敌人关系 
		for(int w = 1; w <= n; w++)
		{
			if(isFriend[v][w])//v和 w是朋友关系 
			{
				if(!isFriend[u][w] && !isEnemy[u][w])//u和w不是朋友不是敌人是陌生人 
				{
					ans--;//符合不握手情况ans-- 
					isEnemy[u][w] = isEnemy[w][u] = true;//让u和w变成敌人,相当于已经被算在答案里不用在被算一次了 
				}
			}
		}
		for(int w = 1; w <= n; w++)
		{
			if(isFriend[u][w])//u和w是朋友关系 
			{
				if(!isFriend[v][w] && !isEnemy[v][w])//v和w不是朋友不是敌人是陌生人 
				{
					ans--;
					isEnemy[v][w] = isEnemy[w][v] = true;//让v和w变成 敌人,相当于已经被算在答案里不要再被算一次了 
				}
			}
		}
	}
	cout << ans << endl;//最终握手次数 
	return 0;
}

F 查找

 题目大意:

要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 −1 。

分析:

二分查找模板题,取这个序列中间的数,然后与要查找的数进行比较

  • 如果要查找的数比中间的数大,说明要查找的数在中间的数的右边
  • 如果要查找的数比中间的数小,说明要查找的数在中间的数的左边
  • 如果要查找的数与中间的数相等,要继续往左边找,不确定这个中间数是否是第一个数

循环直到找完。

#include<iostream>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int a[N];

int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i++) cin >> a[i];
	while(m--)
	{
		int x;
		cin >> x;
		int l = 1, r = n;
		while(l < r)
		{
			int mid = l + r >> 1;
			if(x <= a[mid]) r = mid;
			else l = mid + 1;
		}
		if(a[l] == x) cout << l << ' ';
		else cout << "-1 " ;
	}
	return 0;
}

E cover

题目大意:

一个 n * n 的网格图(标号由 1 开始)上有 m 个探测器,每个探测器有个探测半径 r ,问这 n * n 个点中有多少个点能被探测到。

分析:

由于n和m比较小,可以直接遍历一遍网格上的点,初始时数组赋值为0,判断网格上的点到探测器的距离是否满足 <= r ,如果满足,数组aij标记为1,最后遍历一遍数组,为1表示能被探测器探测到,点的总数+1,最后输出sum

#include<iostream>
#include<cmath>

using namespace std;

int a[110][110];

int main()
{
	int n, m, r;
	cin >> n >> m >> r;
	int sum = 0;
	while(m--)
	{
		int x, y;
		cin >> x >> y;
		for(int i = 1; i <= n; i++)
		{
			for(int j = 1; j <= n; j++)
			{
				if(((i - x) * (i - x) + (j - y) * (j - y)) <= r * r)
				{
					a[i][j] = 1;
				}
			}
		}
	}
	for(int i = 1; i <= n; i++)
		{
			for(int j = 1; j <= n; j++)
			{
				if(a[i][j] == 1)
				{
					sum++;
				}
			}
		}
	cout << sum << endl;
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值