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;
}