CCF-CSP认证 202104-3 DHCP服务器
题意描述
这次要求我们实现一个简易的DHCP服务器 我们需要处理DIS REQ
报文段 发送 NAK ACK OFR
报文段 对于每个接收到的报文段 我们需要判断该报文是否有效 若是有效的DIS
报文 我们要回复OFR
报文 报文中含有服务器编号 请求主机编号 报文类型 申请得到的IP地址 IP地址的过期时刻 若是有效的REQ
报文 我们要根据目的主机的不同 以及IP地址的有效性作出不同的回复
输入输出格式
输入第一行 是服务器的配置 包含 地址池大小 普通时长 最短时长 最长时长 服务器的名字
第二行 是总共有多少个DHCP请求报文段
接下来 便是各个报文 格式为 服务器收到报文的时间 发送主机 目的主机 报文类型 IP地址 希望过期的时刻
我们要输出DHCP服务器的回复
数据规模
地址池最大可以达到1e4
算法设计
- 模拟题最重要的就是存储结构的设计 这里 我们设计一个结构体
IP
有成员onwer ed
我们将1 到 N 的IP地址存储在数组中 下标直接映射为IP地址 这样 从前到后遍历数组 便实现了IP地址从小到大的枚举要求onwer
是该IP地址的占有者ed
是该IP地址的过期时刻 再设计一个flag
数组 下标同样映射数组 值表示着该IP地址的状态 0 表示未分配 1 表示待分配 2 表示占有 3 表示过期 - 将各个遍历直接定义为全局变量 此后函数就不用传参数了
- 题中对服务器要处理的报文类型描述较多 但总结下来 就是 我们只需要处理 类型为
DIS
且 目的主机为*
的 或是 类型为REQ
且 目的主机不为*
的 其余直接忽略 - 接下来对报文类型进行分类 若为
DIS
先求分配的IP地址 循环遍历IP数组 先找占有者是源主机的 若没有 再找状态为未分配的 最后找过期的 若分配不到IP地址 直接跳过 若分配了IP地址 计算过期时间 即可发送报文 - 若为
REQ
先判断目的主机是不是我们 若不是 将数组中所有占有者为源主机的 而且 处于待分配地IP地址地状态均转换为未分配 占有者清空 若目的主机是我们 我们需要在IP数组中寻找该IP地址的占有者是不是源主机 若不是 发送NAK
报文 若是 发送ACK
报文 - 整体的框架就是这样了 但是我们必须注意最重要的细节 提交得到70的 应该就是这些细节没有处理到位
- 细节一 每次我们处理报文前 必须根据现在的接收报文的时刻 和 每个IP地址的过期时刻 将每个IP地址的状态刷新 主要有两个状态转移 第一个是 处于待分配的IP地址在过期之后 转换为未分配的IP地址 占有者清空 第二个是 处于占有状态的IP地址在过期之后转换为过期状态 占有者不变 我们更新状态的原因很简单 处理报文时要分配IP地址 我们必须准确获得此刻每个IP地址的状态 才能作出相应的处理
- 细节二 在处理
REQ
报文时 当目的主机不是我们 我们要枚举所有的IP地址 更新状态 - 我在第一次实现时 顺利得到超时的结果 😂 原因是 我没有写结构体 而是把IP数据用
map<gg,pair<string,gg>>
存储起来 还有map<gg,gg>
表示状态 由于map的键是升序的 我遍历map感觉也能满足要求 但在程序中 我们要频繁地对每个IP地址的状态进行更改 就意味中 我们要频繁地访问状态map map底层是红黑树 由键查找的复杂度达到了O(logN)
果断超时 之后改用数组下标映射IP地址 数组值直接表示IP地址的状态 就过了 所以 以后还是尽量用数组下标做映射 数组的下标就相当于map的键 不过 这个题感觉数据没有说的那么大 平方级算法也是过的去的
希望读者在这些引导之下 能够独立编码 调试 解决问题
c++11代码
#include <bits/stdc++.h>
using namespace std;
using gg = long long;
struct IP{
string owner;
gg ed;
};
vector<IP>mp(1); // 初始长度为1
vector<gg>flag(1); // 下标是 ip地址
gg N, t, tmax, tmin, ni, nowtime, endtime, ip;
string hname, from, to, type; // hname 服务器名字
bool judge(){
if(type =="DIS" and to =="*") return true;
if(type == "REQ" and to != "*") return true;
return false;
}
void brush(){ // 更新状态
for(gg i=1;i<=N;i++){
if(mp[i].ed <= nowtime){
mp[i].ed =0; // 过期时间清零
if(flag[i] ==1) {
flag[i] = 0; // 待分配的超时 转换为未分配
mp[i].owner ="";
}else if(flag[i] == 2){
flag[i] =3; // 占用的超时 转换为 过期
}
}
}
}
gg findip(){
for(gg i =1;i<=N;i++) if(mp[i].owner == from) return i;
for(gg i =1;i<=N;i++) if(flag[i] == 0) return i;
for(gg i =1;i<=N;i++) if(flag[i] == 3) return i;
return 0;
}
gg findtime(){
if(endtime == 0) return nowtime + t;
if(endtime < nowtime + tmin) return nowtime + tmin;
if(endtime > nowtime + tmax) return nowtime + tmax;
return endtime;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> N >> t >> tmax >> tmin >> hname;
cin >> ni;
for (gg i = 1; i <= N;i++){
mp.push_back({"",0});
flag.push_back(0); // 均未分配
}
while (ni--){
cin >> nowtime >> from >> to >> type >> ip >> endtime;
if(not judge()) continue;
brush();
if(type == "DIS"){
gg rip = findip();
if(not rip) continue;
gg time = findtime();
flag[rip] = 1; // 标志为 待分配
mp[rip].owner = from;
mp[rip].ed = time;
cout<<hname<<" "<<from <<" "<<"OFR"<<" "<<rip<<" "<<time<<"\n";
}else{
if(to != hname) { // 我不是目标主机
for(gg i =1;i<=N;i++) {
if(mp[i].owner == from and (flag[i] == 1)) {
flag[i] =0;
mp[i].ed =0; // 时间置0
mp[i].owner = ""; // 所有者为空
}
}
}else{
if(ip >N or mp[ip].owner != from){ // 短路 确保不会越界
cout<<hname<<" "<<from<<" "<<"NAK"<<" "<<ip<<" "<<0<<"\n";
}else{
gg time = findtime();
mp[ip].ed = time;
flag[ip] = 2; // 占用
cout<<hname<<" "<<from<<" "<<"ACK"<<" "<<ip<<" "<<time<<"\n";
}
}
}
}
return 0;
}