#P2670. 第2题-小美架炮
          
                        
                                    
                      
        
              - 
          
          
                      2000ms
            
          
                      Tried: 177
            Accepted: 59
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              美团
                                
            
                        
              时间 :2025年3月8日-开发岗
                              
                      
          
 
- 
                        算法标签>二分算法          
 
第2题-小美架炮
题解
题目分析
题目描述了一个无限大的棋盘,棋盘上有 n 个炮,每个炮的坐标是 (xi,yi)。每个炮的攻击方式如下:
- 选择一个攻击方向(上、下、左、右)。
 - 该方向上看到的第一个棋子为“炮架”。
 - 通过炮架攻击到炮架后面的棋子(只能攻击到炮架后面的第一个)。
 
我们的任务是求出每个炮第一次攻击能攻击到多少个炮。
解题思路
- 
坐标处理:我们需要对每个炮的坐标进行处理,分别统计每个 x 坐标对应的 y 坐标集合,以及每个 y 坐标对应的 x 坐标集合。
 - 
排序:为了快速查找炮架后面的棋子,我们需要对每个 x 坐标对应的 y 坐标集合进行排序,以及对每个 y 坐标对应的 x 坐标集合进行排序。
 - 
查找炮架后面的棋子:对于每个炮,我们分别在四个方向上查找炮架后面的棋子。这里有两种方法,第一种是使用二分查找来确定当前位置,第二种是直接判断前两个坐标和后两个坐标的炮是不是当前的炮。某一侧有两个及以上的炮,就能发动攻击。
 - 
统计结果:对于每个炮,我们统计其在四个方向上能攻击到的炮的数量,并输出结果。
 
代码实现
Python 代码
import sys 
import bisect 
from collections import defaultdict 
 
def main():
    n = int(sys.stdin.readline())  # 读取点的数量 
    mp0 = defaultdict(list)  # 存储每个x对应的y列表 
    mp1 = defaultdict(list)  # 存储每个y对应的x列表 
    p = []  # 存储所有点的坐标 
    for _ in range(n):
        x, y = map(int, sys.stdin.readline().split())  # 读取每个点的x和y 
        mp0[x].append(y)  # 将y添加到x对应的列表中 
        mp1[y].append(x)  # 将x添加到y对应的列表中 
        p.append((x, y))  # 将点坐标添加到列表中 
    for x in mp0:
        mp0[x].sort()  # 对每个x对应的y列表进行排序 
    for y in mp1:
        mp1[y].sort()  # 对每个y对应的x列表进行排序 
    for i in range(n):
        cnt = 0  # 计数器,统计满足条件的点 
        x, y = p[i]  # 获取当前点的坐标 
        if x in mp0:
            k = bisect.bisect_left(mp0[x], y)  # 查找y在x对应的y列表中的位置 
            if k < len(mp0[x]) - 2:  # 如果y后面至少有两个点 
                cnt += 1 
            if k > 1:  # 如果y前面至少有两个点 
                cnt += 1 
        if y in mp1:
            k = bisect.bisect_left(mp1[y], x)  # 查找x在y对应的x列表中的位置 
            if k < len(mp1[y]) - 2:  # 如果x后面至少有两个点 
                cnt += 1 
            if k > 1:  # 如果x前面至少有两个点 
                cnt += 1 
        print(cnt)  # 输出当前点的计数器值 
 
if __name__ == "__main__":
    main()
C++ 代码
#include<bits/stdc++.h>
using namespace std;
 
int main() {
    int n;
    cin >> n;  // 读取点的数量 
    map<int, vector<int>> mp0, mp1;  // mp0存储每个x对应的y列表,mp1存储每个y对应的x列表 
    vector<array<int, 2>> p;  // 存储所有点的坐标 
    for (int i = 0; i < n; i++) {
        int x, y;
        cin >> x >> y;  // 读取每个点的x和y 
        mp0[x].push_back(y);  // 将y添加到x对应的列表中 
        mp1[y].push_back(x);  // 将x添加到y对应的列表中 
        p.push_back({x, y});  // 将点坐标添加到列表中 
    }
    for (auto &[x, y] : mp0) {
        if (!y.empty()) {
            sort(y.begin(), y.end());  // 对每个x对应的y列表进行排序 
        }
    }
    for (auto &[x, y] : mp1) {
        if (!y.empty()) {
            sort(y.begin(), y.end());  // 对每个y对应的x列表进行排序 
        }
    }
    for (int i = 0; i < n; i++) {
        int cnt = 0;  // 计数器,统计满足条件的点 
        int x = p[i][0], y = p[i][1];  // 获取当前点的坐标 
        if (!mp0[x].empty()) {
            int k = lower_bound(mp0[x].begin(), mp0[x].end(), y) - mp0[x].begin();  // 查找y在x对应的y列表中的位置 
            if (k < ((int)mp0[x].size() - 2)) {  // 如果y后面至少有两个点 
                cnt++;
            }
            if (k > 1) {  // 如果y前面至少有两个点 
                cnt++;
            }
        }
        if (!mp1[y].empty()) {
            int k = lower_bound(mp1[y].begin(), mp1[y].end(), x) - mp1[y].begin();  // 查找x在y对应的x列表中的位置 
            if (k < ((int)mp1[y].size() - 2)) {  // 如果x后面至少有两个点 
                cnt++;
            }
            if (k > 1) {  // 如果x前面至少有两个点 
                cnt++;
            }
        }
        cout << cnt << endl;  // 输出当前点的计数器值 
    }
    return 0;
}
Java 代码
import java.util.*; 
 
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in); 
        int n = sc.nextInt();   // 读取点的数量 
        Map<Integer, List<Integer>> xMap = new HashMap<>();  // 存储每个x对应的y列表 
        Map<Integer, List<Integer>> yMap = new HashMap<>();  // 存储每个y对应的x列表 
        List<int[]> p = new ArrayList<>();  // 存储所有点的坐标 
 
        // 读取并存储所有点的坐标 
        for (int i = 0; i < n; i++) {
            int x = sc.nextInt();   // 读取每个点的x 
            int y = sc.nextInt();   // 读取每个点的y 
            xMap.putIfAbsent(x,  new ArrayList<>());  // 初始化x对应的y列表 
            yMap.putIfAbsent(y,  new ArrayList<>());  // 初始化y对应的x列表 
            xMap.get(x).add(y);   // 将y添加到x对应的列表中 
            yMap.get(y).add(x);   // 将x添加到y对应的列表中 
            p.add(new  int[]{x, y});  // 将点坐标添加到列表中 
        }
 
        // 对每个x对应的y列表进行排序 
        for (List<Integer> list : xMap.values())  {
            Collections.sort(list); 
        }
 
        // 对每个y对应的x列表进行排序 
        for (List<Integer> list : yMap.values())  {
            Collections.sort(list); 
        }
 
        // 遍历每个点,统计满足条件的炮架后面的棋子 
        for (int i = 0; i < n; i++) {
            int cnt = 0;  // 计数器,统计满足条件的点 
            int x = p.get(i)[0],  y = p.get(i)[1];   // 获取当前点的坐标 
 
            // 检查上/下方向 
            List<Integer> xList = xMap.get(x); 
            if (xList != null) {
                if (xList.size()  >= 3) {  // 至少有三个点 
                    if (xList.get(0)  != y && xList.get(1)  != y) {  // 前两个点都不等于当前y 
                        cnt++;
                    }
                    if (xList.get(xList.size()  - 1) != y && xList.get(xList.size()  - 2) != y) {  // 后两个点都不等于当前y 
                        cnt++;
                    }
                }
            }
 
            // 检查左/右方向 
            List<Integer> yList = yMap.get(y); 
            if (yList != null) {
                if (yList.size()  >= 3) {  // 至少有三个点 
                    if (yList.get(0)  != x && yList.get(1)  != x) {  // 前两个点都不等于当前x 
                        cnt++;
                    }
                    if (yList.get(yList.size()  - 1) != x && yList.get(yList.size()  - 2) != x) {  // 后两个点都不等于当前x 
                        cnt++;
                    }
                }
            }
 
            System.out.println(cnt);   // 输出当前点的计数器值 
        }
    }
}
        题目内容
在无限大的棋盘中有 n 个炮,第 i 个炮的坐标是(xi,yi)。
已知每个炮的攻击方式是:先选一个攻击方向(上、下、左、右),该方向上看见的第一个棋子为“炮架”,该炮可以通过炮架攻击到炮架后面的棋子(只能攻击到炮架后面的第一个)
小美希望你求出每个炮第一次攻击能攻击到多少个炮。
输入描述
第一行输入一个正整数 n ,代表炮的数量。
接下来的 n 行,每行输入两个整数 xi,yi ,代表每个炮所在的坐标。
1≤n≤105
−105≤xi,yi≤109
输出描述
输出 n 行,每行输出一个整数,代表第 i 个炮可以攻击到的炮的数量。
样例1
输入
6
0 0
0 1
0 2
1 0
2 0
3 0
输出
2
0
1
1
1
1