B3712 [语言月赛202302] 大碗宽面 题解
[语言月赛202302] 大碗宽面 题解
Source & Knowledge
2023 年 2 月语言月赛,由洛谷网校入门计划/基础计划提供。
本题考查二维 bool 数组、枚举技巧。
文字题解
题目大意
有
朋友之间会握手一次,
敌人之间不会握手,
对一对陌生人而言,如果其中一个人的朋友之一是另一个人的敌人,则不握手,否则握手。
求一共握了几次手。
如果令
- 对
20\% 的数据,保证m = 0 。 - 对
50\% 的数据,保证n, m \leq 100 。 - 对
70\% 的数据,保证n, m \leq 10^3 。 - 对
100\% 的数据,保证2 \leq n \leq 3 \times 10^4 ,1 \leq u, v \leq n ,0 \leq p,q \leq m \leq 10^3 。
分析
算法一:均为陌生人的情况
如果
算法二:依照题意枚举
我们可以枚举每对人 ans。
- 如果
i 和j 是朋友,则他们握手,令ans++。 - 如果
i 和j 是敌人,则他们不握手,ans不变。 - 如果
i 和j 是陌生人,则令k 从1 到n 枚举,如果存在一个k 使得k 是i 的朋友且是j 的敌人(或是j 的朋友且是i 的敌人),则符合陌生人之间不握手的条件,ans不变,否则令ans++,记录答案。
判断两人是否是敌人或朋友可以用两个二维数组 isFriend 和 isEnemy 记录。
枚举部分代码如下:
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j) if (isFriend[i][j]) {
++ans;
} else if (!isEnemy[i][j]) {
bool flag = true; // flag 记录是否握手
for (int k = 1; k <= n; ++k)
if ((isFriend[i][k] && isEnemy[k][j]) || (isFriend[j][k] && isEnemy[k][i])) { // 注意判断条件需要加括号
flag = false; // 此时符合不握手条件。
break; // 无需继续枚举
}
if (flag) ++ans; // 如果枚举结束后 flag 仍是 1,则表示不存在不握手的条件,则握手。
}
这一算法需要三层长度为
算法三:优化点对枚举
注意到陌生人点对数量实在是太多了,大约有
首先算出所有陌生人都握手的答案:在算法一中已经解释了一共有
接下来考察一对敌人关系 isFriend[w][v] 是否为 true 即可。每次枚举到一对不握手的陌生人,就令 ans--。
最后一个问题是,答案可能会算重。所以需要一个额外的数组 calced[u][v] 表示我们已经计算过
int enemy[2][maxm];
for (int i = 1; i <= q; ++i) { // 因为要枚举敌人关系,所以读入时需要记录敌人的信息
cin >> enemy[0][i] >> enemy[1][i];
isEnemy[enemy[0][i]][enemy[1][i]] = isEnemy[enemy[1][i]][enemy[0][i]] = true;
}
for (int i = 1; i <= q; ++i) { // 枚举敌人关系
int u = enemy[0][i], v = enemy[1][i];
for (int w = 1; w <= n; ++w) if (isFriend[v][w] && (!isEnemy[u][w]) && (!isFriend[u][w]) && (!calced[u][w])) {
--ans;
calced[u][w] = calced[w][u] = true;
}
swap(u, v); // 交换 u,v 再来一遍,相当于枚举原先 u 的朋友。
for (int w = 1; w <= n; ++w) if (isFriend[v][w] && (!isEnemy[u][w]) && (!isFriend[u][w]) && (!calced[u][w])) {
--ans;
calced[u][w] = calced[w][u] = true;
}
}
这一算法一共需要开三个大小为 calced 数组可以和 isEnemy 数组合并:如果两个人是敌人,那么他们当然已经从答案中被去除了。所以 calced[u][v] 即可以用来表示 calced 数组来表示已经计算过不会握手的人,则可以省去 isEnemy 数组。此时只需要开两个大小为
int enemy[2][maxm];
for (int i = 1; i <= q; ++i) { // 因为要枚举敌人关系,所以读入时需要记录敌人的信息
cin >> enemy[0][i] >> enemy[1][i];
calced[enemy[0][i]][enemy[1][i]] = calced[enemy[1][i]][enemy[0][i]] = true;
}
for (int i = 1; i <= q; ++i) { // 枚举敌人关系
int u = enemy[0][i], v = enemy[1][i];
for (int w = 1; w <= n; ++w) if (isFriend[v][w] && (!isFriend[u][w]) && (!calced[u][w])) {
--ans;
calced[u][w] = calced[w][u] = true;
}
swap(u, v); // 交换 u,v 再来一遍,相当于枚举原先 u 的朋友。
for (int w = 1; w <= n; ++w) if (isFriend[v][w] && (!isFriend[u][w]) && (!calced[u][w])) {
--ans;
calced[u][w] = calced[w][u] = true;
}
}
视频题解
完整代码请在视频中观看。