P7911 [CSP-J 2021] 网络连接 题解
前情提要
考场上写出了这题满分做法(无论是民间数据还是官方数据),可惜少了一句return true;
造成 RE……,于是一等奖变三等奖(可能压线二等),Lemon 衷心希望大家不再犯这样的错误写了这篇题解。
思路
考虑分成两个部分,判断合法和连接服务机(或建立连接)。
第二个部分可以通过 STL 的map
轻易实现:以地址字符串作为下标存储服务机编号即可。
第一个部分整体可以分为三个小部分:是否符合类似1.1.1.1:1
,里面的数是否符合范围,是否含有多余前导零。
2.1 判断是否符合1.1.1.1:1
的形式
从“是否符合这样的形式”出发考虑,把所有的数都变成 \(1\),再判断转出来的字符串是否类似1.1.1.1:1
。
2.2 判断数是否符合范围
这一部分需要判断数是否符合范围。
把问题分成两部分,:
前以及:
后。先扫一遍字符串,找出:
所在的位置,对于:
之前,找到一个.
字符就判断一次这个字符前的数范围合不合法。注意,对于:
前的最后一个数,因为后面没有.
字符,要特别判断一下。
对于:
后,直接扫一遍再判断即可。
- Warning:因为字符串长度可能有 \(25\) 位,所以可能有一个数高达 \(17\) 位,字符串转数的时候要开
long long
,当然你也可以边扫边判断避免溢出。
2.3 判断多余前导零
考虑在第一个非 \(0\) 数字出现前的 \(0\) 数字个数,下面设这个个数为 \(sum\)。
- 当 \(sum=0\) 时,说明没有前导零,通过。
- 当 \(sum=1\) 且这个数长度为 \(1\) 时,说明这个数是 \(0\),通过。
- 其它情况都不通过。
注意为了判断这个数的长度,我们要存下一个数的开始位置。为了方便分离数,可以在原字符串后面加上一个.
字符,每次遇到一个不是数字的字符就后移存数开始位置的变量并检查前面这个数是否合法。
代码
因为这下面都写在函数里,所以你可以理解 \(k\) 为输入的原字符串。
3.1 检查形式
开一个新字符串存储缩略数后的的字符串。
const string ans="1.1.1.1:1";
f="";
bool f2;
for(int i=0;i<k.length();i++){
f2=false;
while(i<k.length()&&k[i]>='0'&&k[i]<='9'){
f2=true;//其实这个有点多余……
i++;//当这个位置是数字时,i就向后移动。
}
if(f2){
f+='1';//把数都缩略成1
}
if(i<k.length()){//若最后是一个数,那么i就会移动到k.length()的位置,所以要特判。
f+=k[i];
}
}
if(f==ans){//如果转出来的串符合格式则返回通过。
return true;
}
return false;
注意这里 \(i\) 可能移动到字符串外,要特判。
3.2 检查数是否在范围内
先找出:
字符的位置:
for(int i=0;i<k.length();i++){
if(k[i]==':'){
add=i;
break;
}
}
再扫描:
前的部分把数转出来依次判断,注意这里存数的变量需要long long
。
ll sum=0;
for(int i=0;i<add;i++){
if(k[i]=='.'){//遇到点字符就要检查
if(sum>255){
return false;
}
sum=0;
}
else{
sum=sum*10+k[i]-'0';//基本的字符串转数操作
}
}
if(sum>255){//因为最后没有点字符,所以单独检查一次。
return false;
}
sum=0;
最后类比刚刚扫描前半部分的方法,扫描:
后的部分:
for(int i=add+1;i<k.length();i++){//从冒号字符后开始扫描
sum=sum*10+k[i]-'0';
}
if(sum>65535){//判断
return false;
}
return true;
3.3 判断多余前导零
往字符串后面增加一个.
字符方便分离数,并初始化第一个数的开始位置为 \(0\):
string t=k;
t+='.';
int kaishi;
kaishi=0;
对于每次遇到非数字的符号进行以下判断:
int sum=0;
for(int j=kaishi;j<i;j++){
if(t[j]=='0'){
sum++;
}
if(t[j]>='1'&&t[j]<='9'){
break;
}
}
if(kaishi==i-1&&sum==1){
kaishi=i+1;
continue;
}
if(sum==0){
kaishi=i+1;
continue;
}
else{
return false;
}
return true;//这句代码,便是惨案。
其中 \(i\) 是目前枚举到的位置(即非数字符号),从这个符号前那个数的开始位置依次向后枚举,统计第一个非 \(0\) 数字出现前 \(0\) 的个数。
最后按照思路中阐述的方法进行判断。注意要记得移动开始位置的下标到下一个数开始的位置。
3.4 主体部分
cin>>n;
for(int i=1;i<=n;i++){
cin>>s;
if(s==fuwu){//这里定义了常量 fuwu 为"Server"。
cin>>idd;
/*这里应该放上刚刚的所有判断函数*/
if(!p.count(idd)){
p[idd]=i;
puts("OK");
continue;
}
if(p.count(idd)){
puts("FAIL");
continue;
}
}
else{
cin>>idd;
/*这里应该放上刚刚的所有判断函数*/
if(!p.count(idd)){
puts("FAIL");
continue;
}
if(p.count(idd)){
cout<<p[idd]<<endl;
continue;
}
}
}
使用一个map
,以地址字符串作为下标,映射到服务机的编号上即可。
AC 代码及后记
AC 代码已经分散给出,注意理解第一。
Lemon 就这样失去了 \(100\) 分。