题目描述
TCP/IP 协议是网络通信领域的一项重要协议。今天你的任务,就是尝试利用这个协议,还原一个简化后的网络连接场景。
在本问题中,计算机分为两大类:服务机(Server
)和客户机(Client
)。服务机负责建立连接,客户机负责加入连接。
需要进行网络连接的计算机共有 nn 台,编号为 1 \sim n1∼n,这些机器将按编号递增的顺序,依次发起一条建立连接或加入连接的操作。
每台机器在尝试建立或加入连接时需要提供一个地址串。服务机提供的地址串表示它尝试建立连接的地址,客户机提供的地址串表示它尝试加入连接的地址。
一个符合规范的地址串应当具有以下特征:
相应地,不符合规范的地址串可能具有以下特征:
- 不是形如
a.b.c.d:e
格式的字符串,例如含有多于 33 个字符.
或多于 11 个字符:
等情况; - 整数 a, b, c, d, ea,b,c,d,e 中某一个或多个超出上述范围;
- 整数 a, b, c, d, ea,b,c,d,e 中某一个或多个含有多余的前导 00。
例如,地址串 192.168.0.255:80
是符合规范的,但 192.168.0.999:80
、192.168.00.1:10
、192.168.0.1:088
、192:168:0:1.233
均是不符合规范的。
如果服务机或客户机在发起操作时提供的地址串不符合规范,这条操作将被直接忽略。
在本问题中,我们假定凡是符合上述规范的地址串均可参与正常的连接,你无需考虑每个地址串的实际意义。
由于网络阻塞等原因,不允许两台服务机使用相同的地址串,如果此类现象发生,后一台尝试建立连接的服务机将会无法成功建立连接;除此之外,凡是提供符合规范的地址串的服务机均可成功建立连接。
如果某台提供符合规范的地址的客户机在尝试加入连接时,与先前某台已经成功建立连接的服务机提供的地址串相同,这台客户机就可以成功加入连接,并称其连接到这台服务机;如果找不到这样的服务机,则认为这台客户机无法成功加入连接。
请注意,尽管不允许两台不同的服务机使用相同的地址串,但多台客户机使用同样的地址串,以及同一台服务机同时被多台客户机连接的情况是被允许的。
你的任务很简单:在给出每台计算机的类型以及地址串之后,判断这台计算机的连接情况。
输入格式
第一行,一个正整数 nn。
接下来 nn 行,每行两个字符串 \mathit{op}, \mathit{ad}op,ad,按照编号从小到大给出每台计算机的类型及地址串。
其中 \mathit{op}op 保证为字符串 Server
或 Client
之一,\mathit{ad}ad 为一个长度不超过 2525 的,仅由数字、字符 .
和字符 :
组成的非空字符串。
每行的两个字符串之间用恰好一个空格分隔开,每行的末尾没有多余的空格。
输出格式
输出共 nn 行,每行一个正整数或字符串表示第 ii 台计算机的连接状态。其中:
如果第 ii 台计算机为服务机,则:
- 如果其提供符合规范的地址串且成功建立连接,输出字符串
OK
。 - 如果其提供符合规范的地址串,但由于先前有相同地址串的服务机而无法成功建立连接,输出字符串
FAIL
。 - 如果其提供的地址串不是符合规范的地址串,输出字符串
ERR
。
如果第 ii 台计算机为客户机,则:
- 如果其提供符合规范的地址串且成功加入连接,输出一个正整数表示这台客户机连接到的服务机的编号。
- 如果其提供符合规范的地址串,但无法成功加入连接时,输出字符串
FAIL
。 - 如果其提供的地址串不是符合规范的地址串,输出字符串
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≤ | 特殊性质 |
---|---|---|
11 | 1010 | 性质 1 2 3 |
2 \sim 32∼3 | 100100 | 性质 1 2 3 |
4 \sim 54∼5 | 10001000 | 性质 1 2 3 |
6 \sim 86∼8 | 10001000 | 性质 1 2 |
9 \sim 119∼11 | 10001000 | 性质 1 |
12 \sim 1312∼13 | 10001000 | 性质 2 |
14 \sim 1514∼15 | 10001000 | 性质 4 |
16 \sim 1716∼17 | 10001000 | 性质 5 |
18 \sim 2018∼20 | 10001000 | 无特殊性质 |
“性质 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 库函数 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.12e4 | float * |
o | 八进制整数。 | int * |
s | 字符串。这将读取连续字符,直到遇到一个空格字符(空格字符可以是空白、换行和制表符)。 | char * |
u | 无符号的十进制整数。 | unsigned int * |
x,X | 十六进制整数。 | int * |
- 附加参数 -- 这个函数接受一系列的指针作为附加参数,每一个指针都指向一个对象,对象类型由 format 字符串中相应的 % 标签指定,参数与 % 标签的顺序相同。
针对检索数据的 format 字符串中的每个 format 说明符,应指定一个附加参数。如果您想要把 sscanf 操作的结果存储在一个普通的变量中,您应该在标识符前放置引用运算符(&),例如:
int n; sscanf (str,"%d",&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