P7911 [CSP-J 2021] 网络连接

博客围绕利用TCP/IP协议还原简化网络连接场景展开。给出题目描述、输入输出格式等,解题思路是通过模拟实现,编写函数判断IP地址合法性,利用sscanf提取IP数字,还需判断前置零,用结构体存储服务机信息,最后完成代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

TCP/IP 协议是网络通信领域的一项重要协议。今天你的任务,就是尝试利用这个协议,还原一个简化后的网络连接场景。

在本问题中,计算机分为两大类:服务机(Server)和客户机(Client)。服务机负责建立连接,客户机负责加入连接。

需要进行网络连接的计算机共有 nn 台,编号为 1 \sim n1∼n,这些机器将按编号递增的顺序,依次发起一条建立连接或加入连接的操作。

每台机器在尝试建立或加入连接时需要提供一个地址串。服务机提供的地址串表示它尝试建立连接的地址,客户机提供的地址串表示它尝试加入连接的地址。

一个符合规范的地址串应当具有以下特征:

相应地,不符合规范的地址串可能具有以下特征:

  1. 不是形如 a.b.c.d:e 格式的字符串,例如含有多于 33 个字符 . 或多于 11 个字符 : 等情况;
  2. 整数 a, b, c, d, ea,b,c,d,e 中某一个或多个超出上述范围;
  3. 整数 a, b, c, d, ea,b,c,d,e 中某一个或多个含有多余的前导 00。

例如,地址串 192.168.0.255:80 是符合规范的,但 192.168.0.999:80192.168.00.1:10192.168.0.1:088192:168:0:1.233 均是不符合规范的。

如果服务机或客户机在发起操作时提供的地址串不符合规范,这条操作将被直接忽略。

在本问题中,我们假定凡是符合上述规范的地址串均可参与正常的连接,你无需考虑每个地址串的实际意义。

由于网络阻塞等原因,不允许两台服务机使用相同的地址串,如果此类现象发生,后一台尝试建立连接的服务机将会无法成功建立连接;除此之外,凡是提供符合规范的地址串的服务机均可成功建立连接。

如果某台提供符合规范的地址的客户机在尝试加入连接时,与先前某台已经成功建立连接的服务机提供的地址串相同,这台客户机就可以成功加入连接,并称其连接到这台服务机;如果找不到这样的服务机,则认为这台客户机无法成功加入连接。

请注意,尽管不允许两台不同的服务机使用相同的地址串,但多台客户机使用同样的地址串,以及同一台服务机同时被多台客户机连接的情况是被允许的。

你的任务很简单:在给出每台计算机的类型以及地址串之后,判断这台计算机的连接情况。

输入格式

第一行,一个正整数 nn。

接下来 nn 行,每行两个字符串 \mathit{op}, \mathit{ad}op,ad,按照编号从小到大给出每台计算机的类型及地址串。

其中 \mathit{op}op 保证为字符串 Server 或 Client 之一,\mathit{ad}ad 为一个长度不超过 2525 的,仅由数字、字符 . 和字符 : 组成的非空字符串。

每行的两个字符串之间用恰好一个空格分隔开,每行的末尾没有多余的空格。

输出格式

输出共 nn 行,每行一个正整数或字符串表示第 ii 台计算机的连接状态。其中:

如果第 ii 台计算机为服务机,则:

  1. 如果其提供符合规范的地址串且成功建立连接,输出字符串 OK
  2. 如果其提供符合规范的地址串,但由于先前有相同地址串的服务机而无法成功建立连接,输出字符串 FAIL
  3. 如果其提供的地址串不是符合规范的地址串,输出字符串 ERR

如果第 ii 台计算机为客户机,则:

  1. 如果其提供符合规范的地址串且成功加入连接,输出一个正整数表示这台客户机连接到的服务机的编号。
  2. 如果其提供符合规范的地址串,但无法成功加入连接时,输出字符串 FAIL
  3. 如果其提供的地址串不是符合规范的地址串,输出字符串 ERR

输入输出样例

输入1

5
Server 192.168.1.1:8080
Server 192.168.1.1:8080
Client 192.168.1.1:8080
Client 192.168.1.1:80
Client 192.168.1.1:99999

输出1

OK
FAIL
1
FAIL
ERR

输入2

10
Server 192.168.1.1:80
Client 192.168.1.1:80
Client 192.168.1.1:8080
Server 192.168.1.1:80
Server 192.168.1.1:8080
Server 192.168.1.999:0
Client 192.168.1.1.8080
Client 192.168.1.1:8080
Client 192.168.1.1:80
Client 192.168.1.999:0

输出2

OK
1
FAIL
FAIL
OK
ERR
ERR
5
1
ERR

说明/提示

【样例解释 #1】

计算机 11 为服务机,提供符合规范的地址串 192.168.1.1:8080,成功建立连接;

计算机 22 为服务机,提供与计算机 11 相同的地址串,未能成功建立连接;

计算机 33 为客户机,提供符合规范的地址串 192.168.1.1:8080,成功加入连接,并连接到服务机 11;

计算机 44 为客户机,提供符合规范的地址串 192.168.1.1:80,找不到服务机与其连接;

计算机 55 为客户机,提供的地址串 192.168.1.1:99999 不符合规范。

【数据范围】

测试点编号n \len≤特殊性质
111010性质 1 2 3
2 \sim 32∼3100100性质 1 2 3
4 \sim 54∼510001000性质 1 2 3
6 \sim 86∼810001000性质 1 2
9 \sim 119∼1110001000性质 1
12 \sim 1312∼1310001000性质 2
14 \sim 1514∼1510001000性质 4
16 \sim 1716∼1710001000性质 5
18 \sim 2018∼2010001000无特殊性质

“性质 1”为:保证所有的地址串均符合规范;
“性质 2”为:保证对于任意两台不同的计算机,如果它们同为服务机或者同为客户机,则它们提供的地址串一定不同;
“性质 3”为:保证任意一台服务机的编号都小于所有的客户机;
“性质 4”为:保证所有的地址串均形如 a.b.c.d:e 的格式,其中 a, b, c, d, ea,b,c,d,e 均为不超过 {10}^9109 且不含有多余前导 00 的非负整数;
“性质 5”为:保证所有的地址串均形如 a.b.c.d:e 的格式,其中 a, b, c, d, ea,b,c,d,e 均为只含有数字的非空字符串。

对于 100 \%100% 的数据,保证 1 \le n \le 10001≤n≤1000。

(题目出自于洛谷)

思路

这道题可能一开始看不懂

但仔细看看就可以就可以发现

这道题是让你写程序判断

服务机:

1.IP地址合不合法

2.之前有没有别的服务机使用这个IP成功创建连接

客户机:

1.IP地址合不合法

2.有没有同一IP的服务机可以连接

发现没

妥妥的模拟

1.同样都有判断“IP地址合不合法”

那就写一个fun函数来判断

题目中的条件

先看第一条

必须形如 a.b.c.d:e 的格式,其中 a, b, c, d, ea,b,c,d,e 均为非负整数;

这个我们不需要多想

只用判断"."和":"的个数就行了

简简单单遍历一下字符串

//判断字符串格式
    for(i=0;i<strlen(ip);i++)//遍历字符串
    {
        if(ip[i]=='.')len1++;//寻找.的个数
        if(ip[i]==':')len2++;//寻找:的个数
    }
    if(!(len1==3))//判断:和.个数是否正确
        return 1;
    if(!(len2==1))
        return 1;//不够则返回1

这里我们设置返回1来表示此IP不合法

再看第二个条件

 a.b.c.d:e

我们该怎么把a,b,c,d,e从字符串提取出来呢

这个时候我们就用到了sscanf

C 库函数 - sscanf()

 C 标准库 - <stdio.h>

描述

C 库函数 int sscanf(const char *str, const char *format, ...) 从字符串读取格式化输入。

声明

下面是 sscanf() 函数的声明。

int sscanf(constchar*str,constchar*format,...)

参数

  • str -- 这是 C 字符串,是函数检索数据的源。
  • format -- 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符
    format 说明符形式为 [=%[*][width][modifiers]type=],具体讲解如下:
参数描述
*这是一个可选的星号,表示数据是从流 stream 中读取的,但是可以被忽视,即它不存储在对应的参数中。
width这指定了在当前读取操作中读取的最大字符数。
modifiers为对应的附加参数所指向的数据指定一个不同于整型(针对 d、i 和 n)、无符号整型(针对 o、u 和 x)或浮点型(针对 e、f 和 g)的大小: h :短整型(针对 d、i 和 n),或无符号短整型(针对 o、u 和 x) l :长整型(针对 d、i 和 n),或无符号长整型(针对 o、u 和 x),或双精度型(针对 e、f 和 g) L :长双精度型(针对 e、f 和 g)
type一个字符,指定了要被读取的数据类型以及数据读取方式。具体参见下一个表格。

sscanf 类型说明符:

类型合格的输入参数的类型
c单个字符:读取下一个字符。如果指定了一个不为 1 的宽度 width,函数会读取 width 个字符,并通过参数传递,把它们存储在数组中连续位置。在末尾不会追加空字符。char *
d十进制整数:数字前面的 + 或 - 号是可选的。int *
e,E,f,g,G浮点数:包含了一个小数点、一个可选的前置符号 + 或 -、一个可选的后置字符 e 或 E,以及一个十进制数字。两个有效的实例 -732.103 和 7.12e4float *
o八进制整数。int *
s字符串。这将读取连续字符,直到遇到一个空格字符(空格字符可以是空白、换行和制表符)。char *
u无符号的十进制整数。unsigned int *
x,X十六进制整数。int *
  • 附加参数 -- 这个函数接受一系列的指针作为附加参数,每一个指针都指向一个对象,对象类型由 format 字符串中相应的 % 标签指定,参数与 % 标签的顺序相同。

    针对检索数据的 format 字符串中的每个 format 说明符,应指定一个附加参数。如果您想要把 sscanf 操作的结果存储在一个普通的变量中,您应该在标识符前放置引用运算符(&),例如:

    int n;
        sscanf (str,"%d",&amp;n);

返回值

如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。

(出自菜鸟教程C 库函数 – sscanf() | 菜鸟教程 (runoob.com)

大概就是

sscanf(ip,"%d.%d.%d.%d:%d",&a,&b,&c,&d,&e);//运用sscanf将ip地址中的数字转换成整型并存储

此时a,b,c,d,e存储的就是IP中的数字,只不过是整型的

接下来只需要比较在不在范围内就行了

//判断范围
if(!(a>=0&&a<=255&&b>=0&&b<=255&&c>=0&&c<=255&&d>=0&&d<=255&&e>=0&&e<=65535))//用最直接的方法把比较过程列一遍
return 1;

最后

还要判断前置零

什么是前置零?

就是像这样

192.168.01.01:8022

标记出来的就是前置零

还记不记得sscanf可以把字符串里的数字转换成整型并存储在变量里

没错

存储的时候是整型

也就是说如果有前置零会自动省略

我们只需要比较转换前后的字符串长度就行了

怎么问题来了

整型怎么和字符串比较

很简单

转换回去

这里就要用到sprintf——一个用处与sscanf相反的函数

用它把整型带符号转换回去

char m[25];//定义变量
sprintf(m,"%d.%d.%d.%d:%d",a,b,c,d,e);//把转换后的ip地址再转换回来,因为sscanf把ip地址转换成整型后会自动省略0
if(strlen(m)!=strlen(ip))return 1;//判断省略0后的字符串长度是否不变

注意sscanf和sprintf只能用在char类型的字符串

所以函数部分就写完了

int fun(char ip[])//判断ip是否合法,数组传参方法比较特殊
{
    int a,b,c,d,e,i,len1=0,len2=0;//定义变量
    sscanf(ip,"%d.%d.%d.%d:%d",&a,&b,&c,&d,&e);//运用sscanf将ip地址中的数组转换成整型并存储

    //判断字符串格式
    for(i=0;i<strlen(ip);i++)//遍历字符串
    {
        if(ip[i]=='.')len1++;//寻找.的个数
        if(ip[i]==':')len2++;//寻找:的个数
    }
    if(!(len1==3))//判断:和.个数是否足够
        return 1;
    if(!(len2==1))
        return 1;//不够则返回1

    //判断范围
    if(!(a>=0&&a<=255&&b>=0&&b<=255&&c>=0&&c<=255&&d>=0&&d<=255&&e>=0&&e<=65535))//用最直接的方法把比较过程列一遍
    return 1;

    //判断前置零
    char m[25];//定义变量
    sprintf(m,"%d.%d.%d.%d:%d",a,b,c,d,e);//把转换后的ip地址再转换回来,因为sscanf把ip地址转换成整型后会自动省略0
    if(strlen(m)!=strlen(ip))return 1;//判断省略0后的字符串长度是否不变

    return 0;//如果以上判断进行完后都没有返回1,则说明ip地址合法
}

接下来判断服务机IP是否被用过

这个时候

我们需要一个结构体

用来存储创建成功的服务机的IP和编号

struct node//创建结构体数组
{
    string ip;//保存地址
    int num;//保存编号
}g[1005];//!!!!!!!!!!!!重点!!!!!!!!!!!!数组长度千万要>=550,否则会超时

每一次判断时都会遍历一遍结构体数组

如果没找到

就视为创建成功

并将其保存下来

flag=0;//初始化状态
            for(j=1;j<=c;j++)//遍历连接成功的服务机
            {
                if(g[j].ip==y)//如果有重复的ip说明,ip被使用
                {
                    flag=1;//更新状态
                    break;//跳过遍历
                }
            }
            if(flag!=1)//如果ip没被使用
            {
                g[++c].num=i;//更新库并保存编号
                g[c].ip=y;//保存ip
                cout<<"OK"<<endl;//输出OK表示连接成功
            }
            else//如果ip被使用
            {
                cout<<"FAIL"<<endl;//输出FAIL表示连接失败
            }

再看客户机

判断一下有没有同IP的服务机,有输出服务机编号,没有则输出FAIL;

同上

一个遍历就解决了

flag=0;//初始化状态
            for(j=1;j<=c;j++)//遍历连接成功的服务机
            {
                if(g[j].ip==y)//如果发现了连接成功的服务机的ip与当前客户机的ip相同
                {
                    flag=1;//更新状态
                    cout<<g[j].num<<endl;//输出连接成功的服务机编号
                    break;//!!!!!!注意!!!!!!!不能是continue,否则会超时
                }

            }
            if(flag!=1)cout<<"FAIL"<<endl;//如果遍历完

于是

模拟就结束了

代码

#include<bits/stdc++.h>
using namespace std;
int fun(char ip[])//判断ip是否合法
{
    int a,b,c,d,e,i,len1=0,len2=0;//定义变量
    sscanf(ip,"%d.%d.%d.%d:%d",&a,&b,&c,&d,&e);//运用sscanf将ip地址中的数组转换成整型并存储

    //判断字符串格式
    for(i=0;i<strlen(ip);i++)//遍历字符串
    {
        if(ip[i]=='.')len1++;//寻找.的个数
        if(ip[i]==':')len2++;//寻找:的个数
    }
    if(!(len1==3))//判断:和.个数是否足够
        return 1;
    if(!(len2==1))
        return 1;//不够则返回1

    //判断范围
    if(!(a>=0&&a<=255&&b>=0&&b<=255&&c>=0&&c<=255&&d>=0&&d<=255&&e>=0&&e<=65535))//用最直接的方法把比较过程列一遍
    return 1;

    //判断前置零
    char m[25];//定义变量
    sprintf(m,"%d.%d.%d.%d:%d",a,b,c,d,e);//把转换后的ip地址再转换回来,因为sscanf把ip地址转换成整型后会自动省略0
    if(strlen(m)!=strlen(ip))return 1;//判断省略0后的字符串长度是否不变

    return 0;//如果以上判断进行完后都没有返回1,则说明ip地址合法
}
struct node//创建结构体数组
{
    string ip;//保存地址
    int num;//保存编号
}g[1005];//!!!!!!!!!!!!重点!!!!!!!!!!!!数组长度千万要>=550,否则会超时
int main()
{
    string op;//创建字符串
    char ad[2000];//同上,只是类型不同
    int n,i,j,flag,c;//定义变量
    cin>>n;//输入数据个数
    for(i=1;i<=n;i++)//循环输入
    {
        cin>>op>>ad;//输入类型和ip地址
        if(fun(ad)==1)//传值到fun进行判断是否合法
        {
            cout<<"ERR"<<endl;
            continue;//如果不合法,输出并跳过下面的判断
        }
        string y=ad;//创建字符串,将char类型的ad强制转换为string类型的y,方便比较
        if(op=="Server")//判断是服务机还是客户机,进行不同的判断
        {
            flag=0;//初始化状态
            for(j=1;j<=c;j++)//遍历连接成功的服务机
            {
                if(g[j].ip==y)//如果有重复的ip说明,ip被使用
                {
                    flag=1;//更新状态
                    break;//跳过遍历
                }
            }
            if(flag!=1)//如果ip没被使用
            {
                g[++c].num=i;//更新库并保存编号
                g[c].ip=y;//保存ip
                cout<<"OK"<<endl;//输出OK表示连接成功
            }
            else//如果ip被使用
            {
                cout<<"FAIL"<<endl;//输出FAIL表示连接失败
            }

        }
        else//如果是客户机
        {
            flag=0;//初始化状态
            for(j=1;j<=c;j++)//遍历连接成功的服务机
            {
                if(g[j].ip==y)//如果发现了连接成功的服务机的ip与当前客户机的ip相同
                {
                    flag=1;//更新状态
                    cout<<g[j].num<<endl;//输出连接成功的服务机编号
                    break;//!!!!!!注意!!!!!!!不能是continue,否则会超时
                }

            }
            if(flag!=1)cout<<"FAIL"<<endl;//如果遍历完后没有发现连接成功的服务机的ip与当前客户机的ip相同,则输出FAIL
        }
    }
    return 0;//奥赛必打
}

最后

喜提AC

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值