[CSP-J 2021] 网络连接

GitHub跳转原题关系图返回列表

先严格校验地址串格式,再用映射表维护成功服务机地址与编号,按顺序处理建立和加入连接。

OJ: luogu

题目 ID: P7911

难度:普及/提高-

标签:字符串模拟

日期: 2026-06-19 10:41

题意

有若干台机器依次发起操作,每次要么是:

  • Server 尝试建立连接;
  • Client 尝试加入连接。

每台机器都会给出一个地址串。

你需要先判断地址串是否合法,再根据题目规则输出:

  • ERR
  • OK
  • FAIL
  • 或连接到的服务机编号

思路

这题分两层处理最清楚。

最直接的教学版写法如下:

cpp
// brute.cpp:按题意直接校验地址并维护已成功建立连接的服务机,作为教学版和对拍基准程序。
#include <bits/stdc++.h>
using namespace std;

int n;
map<string, int> server_id;

// 读取一个必须以 delim 结尾的数字段,并检查范围与前导零。
bool parse_token(const string &s, int &pos, int limit, char delim) {
    int n = (int) s.size();
    if (pos >= n || s[pos] < '0' || s[pos] > '9') {
        return false;
    }

    int start = pos;
    long long value = 0;
    while (pos < n && s[pos] >= '0' && s[pos] <= '9') {
        value = value * 10 + (s[pos] - '0');
        pos++;
    }

    if (pos - start > 1 && s[start] == '0') {
        return false;
    }
    if (value > limit) {
        return false;
    }
    if (pos >= n || s[pos] != delim) {
        return false;
    }

    pos++;
    return true;
}

bool parse_last_token(const string &s, int &pos, int limit) {
    int n = (int) s.size();
    if (pos >= n || s[pos] < '0' || s[pos] > '9') {
        return false;
    }

    int start = pos;
    long long value = 0;
    while (pos < n && s[pos] >= '0' && s[pos] <= '9') {
        value = value * 10 + (s[pos] - '0');
        pos++;
    }

    if (pos - start > 1 && s[start] == '0') {
        return false;
    }
    if (value > limit) {
        return false;
    }
    return pos == n;
}

bool valid_address(const string &s) {
    int pos = 0;
    if (!parse_token(s, pos, 255, '.')) return false;
    if (!parse_token(s, pos, 255, '.')) return false;
    if (!parse_token(s, pos, 255, '.')) return false;
    if (!parse_token(s, pos, 255, ':')) return false;
    if (!parse_last_token(s, pos, 65535)) return false;
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        string type, addr;
        cin >> type >> addr;

        if (!valid_address(addr)) {
            cout << "ERR\n";
            continue;
        }

        if (type == "Server") {
            if (server_id.count(addr)) {
                cout << "FAIL\n";
            }
            else {
                server_id[addr] = i;
                cout << "OK\n";
            }
        }
        else {
            if (server_id.count(addr)) {
                cout << server_id[addr] << '\n';
            }
            else {
                cout << "FAIL\n";
            }
        }
    }

    return 0;
}

第一层是地址校验。

地址必须严格形如 a.b.c.d:e,而且:

  • 前四段范围在 02550 \dots 255
  • 端口范围在 0655350 \dots 65535
  • 不能有多余前导零

第二层是业务逻辑。

我们用一个 map 记录“哪些地址已经被成功建立连接的服务机占用,以及它对应哪台机器编号”。

于是:

  • Server:地址合法且未被占用,输出 OK 并记录;否则 FAIL
  • Client:地址合法且能找到服务机,输出该服务机编号;否则 FAIL

代码

cpp
#include <bits/stdc++.h>
using namespace std;

int n;
map<string, int> server_id;

bool parse_token(const string &s, int &pos, int limit, char delim) {
    int n = (int) s.size();
    if (pos >= n || s[pos] < '0' || s[pos] > '9') {
        return false;
    }

    int start = pos;
    long long value = 0;
    while (pos < n && s[pos] >= '0' && s[pos] <= '9') {
        value = value * 10 + (s[pos] - '0');
        pos++;
    }

    if (pos - start > 1 && s[start] == '0') {
        return false;
    }
    if (value > limit) {
        return false;
    }
    if (pos >= n || s[pos] != delim) {
        return false;
    }

    pos++;
    return true;
}

bool parse_last_token(const string &s, int &pos, int limit) {
    int n = (int) s.size();
    if (pos >= n || s[pos] < '0' || s[pos] > '9') {
        return false;
    }

    int start = pos;
    long long value = 0;
    while (pos < n && s[pos] >= '0' && s[pos] <= '9') {
        value = value * 10 + (s[pos] - '0');
        pos++;
    }

    if (pos - start > 1 && s[start] == '0') {
        return false;
    }
    if (value > limit) {
        return false;
    }
    return pos == n;
}

bool valid_address(const string &s) {
    int pos = 0;
    if (!parse_token(s, pos, 255, '.')) return false;
    if (!parse_token(s, pos, 255, '.')) return false;
    if (!parse_token(s, pos, 255, '.')) return false;
    if (!parse_token(s, pos, 255, ':')) return false;
    if (!parse_last_token(s, pos, 65535)) return false;
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        string type, addr;
        cin >> type >> addr;

        if (!valid_address(addr)) {
            cout << "ERR\n";
            continue;
        }

        if (type == "Server") {
            if (server_id.count(addr)) {
                cout << "FAIL\n";
            }
            else {
                server_id[addr] = i;
                cout << "OK\n";
            }
        }
        else {
            if (server_id.count(addr)) {
                cout << server_id[addr] << '\n';
            }
            else {
                cout << "FAIL\n";
            }
        }
    }

    return 0;
}

复杂度

  • 时间复杂度:O(nL)O(nL)
  • 空间复杂度:O(nL)O(nL)

其中 L 是地址串长度。

总结

这题核心不在映射表,而在地址校验写得够不够严谨。

把地址解析单独写成固定 5 段检查,代码会稳定很多。