判断地址类型
背景知识
IP 地址是 IP 协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
最初设计互联网络时,为了便于寻址以及层次化构造网络,每个IP地址包括两个标识码(ID),即网络 ID 和主机 ID 。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机 ID 与其对应。 IP 地址根据网络ID的不同分为5种类型,A 类地址、B 类地址、C 类地址、 D 类地址和 E 类地址。
类型 | 范围 |
---|---|
A | 0.0.0.0 ~ 127.255.255.255 |
B | 128.0.0.0 ~ 191.255.255.255 |
C | 192.0.0.0 ~ 224.255.255.255 |
D | 224.0.0.0 ~ 239.255.255.255 |
E | 240.0.0.0 ~ 255.255.255.255 |
其中,A类地址中 127.x.x.x 的子段是内网地址的保留段,在本题中,我们使用大写字母 “O” 来标识;其他类型的地址均使用本身的字母标识。
题目描述
Hillle 在一家互联网大厂担任网络管理员,他想让你编写一个程序,帮他统计他的数据集中有多少个 A,B,C,D,E 类地址?
如果你遇到了内网地址段,你无需把它计入 A 类地址中,而需计在 O 类地址下。
此外,Hillle 的数据集中可能存在错误的 ip 地址,你需要过滤出它们并统计数量(用 “W” 标识)。
输入格式
第一行包括一个整数 (n),代表数据集中的 ip 地址数量;
接下来 (n) 行每行表示一个 ip 地址,四个子段以 (.) 隔开,并保证为正整数。
输出格式
请参考样例。
输入样例 1
3
250.106.183.223
226.103.155.98
220.247.34.151
输出样例 1
A : 0
B : 0
C : 1
D : 1
E : 1
O : 0
W : 0
输入样例 2
3
127.0.0.1
256.103.155.98
147.1.34.151
输出样例 2
A : 0
B : 1
C : 0
D : 0
E : 0
O : 1
W : 1
——————————————————————————————————————
纯粹的模拟题,会处理字符串就可以了,具体做法请参考代码~
代码如下
#include <bits/stdc++.h>
using namespace std;
int n,fl,d[4],cnt[7];
string ip;
inline bool work() {
int t=0; memset(d,0,sizeof(d));
for (int i=0;i<(int)ip.size();i++)
if (ip[i]=='.') t++; else d[t]=d[t]*10+ip[i]-48;
if (d[0]<127) fl=1; else if (d[0]==127) fl=0;
else if (d[0]<192) fl=2; else if (d[0]<224) fl=3;
else if (d[0]<240) fl=4; else fl=5;
for (int i=0;i<4;i++) if (d[i]>255) return false;
return true;
}
int main() {
cin>>n;
while (n--) cin>>ip,cnt[work()?fl:6]++;
printf("A : %d
B : %d
C : %d
D : %d
",cnt[1],cnt[2],cnt[3],cnt[4]);
printf("E : %d
O : %d
W : %d
",cnt[5],cnt[0],cnt[6]);
return 0;
}
解题思路
回文地址
题目描述
Hillle 在一家互联网大厂担任网络管理员,在日常工作中,他喜欢美丽的 IP 地址,美丽的条件如下:
在一行中写出 IP 地址所有数字(去除逗号)所得的字符串为回文串。
例如:地址 12.102.20.121 和 0.3.14.130 是美丽的(字符串 “1210220121” 和 “0314130” 是回文串),
而地址 1.20.20.1 和 100.4.4.1 则不是。
Hillle 希望找到所有具有给定数字集组成的美丽 IP 地址,集合中的每个数字必须在IP地址中至少出现一次,且不能包含任何其他数字。
你能帮助他完成这项工作吗?
注意:所有的 IP 均为 IPv4 协议下的地址。
输出格式
第一行包括一个整数 (n),表示给定集合中的数字数量;
第二行包括 (n) 个整数,即集合本身。
输出格式
第一行包括一个整数,表示使用给定集合可以组成的美丽的 IP 地址的数量。
接下来若干行则按顺序输出这些美丽的 IP 地址。
规则如下:先以 IP 的首段从小大到大排序,若相同则以第二段从小到大排序,第三四段同理。
输入样例 1
6
0 1 2 9 8 7
输出样例 1
6
78.190.209.187
79.180.208.197
87.190.209.178
89.170.207.198
97.180.208.179
98.170.207.189
输入样例 2
1
4
输出样例 2
16
4.4.4.4
4.4.4.44
4.4.44.4
4.4.44.44
4.44.4.4
4.44.4.44
4.44.44.4
4.44.44.44
44.4.4.4
44.4.4.44
44.4.44.4
44.4.44.44
44.44.4.4
44.44.4.44
44.44.44.4
44.44.44.44
——————————————————————————————————————
解题思路
题目大意:给定 (n) 个整数,用它们来组成一个合法的回文 IP 地址,每个整数都要用到
我们可以先无视分割的点,用 dfs 暴力枚举前一半,后一半就可以用回文的性质得到
这里有一个小技巧是可以使用一个二进制数表示集合,例如选用了数字 1,3,5 就可以用 21 (10101) 表示
奇数和偶数个数字组成的 ip 处理方式不同:
奇数:<上一个序列> + <当前遍历到的数> + <倒的上一个序列>
偶数:<上一个序列> + <当前遍历到的数> * 2 + <倒的上一个序列>
接下来需要用三个点把得出的序列划分成四个段,可以直接用三重循环枚举所有可能性(再代码中有一些优化的小技巧)
如果每个段的数值都是非法的话,则可以计入最终答案,并用一个 node 结构体记录每段的值,最终进行一次排序
如果能灵活地运用 vector 容器,则可以节省不少的代码量~
代码如下
#include <bits/stdc++.h>
using namespace std;
int n,tar,ans,v[23];
vector<int> now,pt;
struct node {
// 由于最后需要按顺序输出,所以要储存 ip 各字段的值并重载
int p1,p2,p3,p4;
string pri;
bool operator < (const node &T) const {
if (p1!=T.p1) return p1<T.p1;
else if (p2!=T.p2) return p2<T.p2;
else if (p3!=T.p3) return p3<T.p3;
else return p4<T.p4;
}
};
vector<node> book;
inline int readint() {
int X=0,w=0; char ch=0;
while (!isdigit(ch)) w|=ch=='-',ch=getchar();
while (isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
inline bool check(int s,int t) {
// 验证 ip 某字段的合法性
if (!pt[s] && s!=t) return false; // 对于 0 进行特判
int num=0;
for (int i=s;i<=t;i++) num=num*10+pt[i];
if (num>255) return false;
else return true;
}
inline int sum(int s,int t) {
int num=0;
for (int i=s;i<=t;i++) num=num*10+pt[i];
return num;
}
inline void divide() {
int m=(int)pt.size();
for (int i=0;i<min(3,m-3);i++) {
for (int j=i+1;j<min(6,m-2);j++) {
for (int k=j+1;k<m-1;k++) {
// 划分为四个段:[0,i] [i+1,j] [j+1,k] [k+1,m-1]
// 再逐一进行合法性判断
if (check(0,i) && check(i+1,j) && check(j+1,k) && check(k+1,m-1)) {
node tmp;
tmp.p1=sum(0,i),tmp.p2=sum(i+1,j),tmp.p3=sum(j+1,k),tmp.p4=sum(k+1,m-1);
for (int t=0;t<m;t++) {
tmp.pri+=char('0'+pt[t]);
if (t==i || t==j || t==k) tmp.pri+='.';
}
book.push_back(tmp),ans++;
}
}
}
}
}
void dfs(int m,int use) {
if (m>6) return;
for (int i=1;i<=n;i++) {
if ((use|(1<<v[i]))==tar) { // 条件为给的集合中的数都用上了
// 奇数:<上一个序列> + <当前遍历到的数> + <倒的上一个序列>
// rbegin() 和 rend() 是倒序迭代器
pt.clear(),pt=now,pt.push_back(v[i]);
pt.insert(pt.end(),now.rbegin(),now.rend());
divide();
// 偶数:<上一个序列> + <当前遍历到的数> * 2 + <倒的上一个序列>
pt=now,pt.push_back(v[i]);
pt.insert(pt.end(),pt.rbegin(),pt.rend());
divide();
}
now.push_back(v[i]);
dfs(m+1,use|(1<<v[i]));
now.pop_back();
}
}
int main() {
n=readint();
for (int i=1;i<=n;i++) v[i]=readint(),tar|=(1<<v[i]);
// 两个排序,第一个是为了优化效率,第二个是保证题目要求的输出顺序
sort(v+1,v+n+1),dfs(1,0),sort(book.begin(),book.end()),cout<<ans<<'
';
for (int i=0;i<ans;i++) cout<<book[i].pri<<'
';
return 0;
}
经营与开发
题目描述
4X 概念体系,是指在 PC 战略游戏中一种相当普及和成熟的系统概念,得名自 4 个同样以 "EX" 为开头的英语单词。
eXplore(探索)
eXpand(拓张与发展)
eXploit(经营与开发)
eXterminate(征服)
—— Wiki
今次我们着重考虑 exploit 部分,并将其模型简化:
你驾驶着一台带有钻头(初始能力值 (w) )的飞船,按既定路线依次飞过n个星球。
星球笼统的分为2类:资源型和维修型。( p 为钻头当前能力值)
- 资源型:含矿物质量 (a[i]) ,若选择开采,则得到 (a[i]*p) 的金钱,之后钻头损耗 (k%) ,即 (p=p*(1-0.01k))
- 维修型:维护费用 (b[i]) ,若选择维修,则支付 (b[i]*p) 的金钱,之后钻头修复 (c %),即 (p=p*(1+0.01c))
注:维修后钻头的能力值可以超过初始值(你可以认为是翻修+升级)
请作为舰长的你仔细抉择以最大化收入。
输入格式
第一行包括 (4) 个整数 (n,k,c,w) 。
以下 (n) 行,每行 (2) 个整数 (type,x) 。
(type) 为 (1)则代表其为资源型星球,(x) 为其矿物质含量 (a[i]) ;
(type) 为 (2) 则代表其为维修型星球,(x) 为其维护费用 (b[i]) ;
输出格式
一个实数(保留 (2) 位小数),表示最大的收入。
输入样例
5 50 50 10
1 10
1 20
2 10
2 20
1 30
输出样例
375.00
数据范围
对于 (30%) 的数据 (n<=100) ;
另有 (20%) 的数据 (n<=1000;k=100) ;
对于 (100%) 的数据 (n<=100000; 0<=k,c,w,a[i],b[i]<=100) ;
保证答案不超过 (10^9)
——————————————————————————————————————
“依次飞过 (n) 个星球”,第一反应就是动态规划,然后验证下无后效性即可。
这题关键点为状态的设计。
Method 1
(F[i][x]) 表示到达第 (i) 个星球,且钻头能力值为 (x) 的最大收入值。
(x) 因为是实数,所以要取一定的精度。对于数值范围都在 (100) 的本题,(n) 在 (10) 左右的时候毫无压力。
时空复杂度:(O(n*x)); (x) 为精度范围。
期望得分:(10-30)
Method 2
(F[i][x][y]) 表示到达第 (i) 个星球,且之前开采过 (x) 次,维修过 (y) 次。
因为本题开采和维修对钻头的影响都是定值。所以钻头能力就是 (w*k^x*c^y)
时空复杂度: (O(n^3))
期望得分:(30)
Method 3
对于 (20\% k=100) 的数据,钻头开采一次就永久损坏了。所以只需记录维修过几次即可。
时空复杂度: (O(n^2))
期望得分:(20)(配合 Method 2 为 (50) )
Method 4
与 Method 2 一样的状态设计。但是 (x,y) 的范围不需要与 (n) 相同。因为在随机情况下,开采和维修的次数寥寥无几(结合次幂考虑)。
时空复杂度: (O(n*x*y)); (x,y) 为选手选择的范围
期望得分:(30-80)
Method 5
与 Method 2 一样的状态设计,但是使用 BFS 来进行 DP 过程,这样就不会遍历到没有被访问到的状态,
同时选手可以自己加上一些简单的贪心判断来减少状态数量。
时空复杂度: (O(?))
期望得分:(70-100)
Method 6
与前 (5) 种做法截然不同。前 (5) 种做法的最大瓶颈就是“当前钻头能力”,下面我们尝试不存储“当前钻头能力”。
(F[i]) 表示前 (i) 个星球的最优收入。很明显这是不行的,因为当前钻头能力会切实影响到后面的过程,不严谨的说,当前钻头能力有“后效性”。
但是这个当前钻头能力对后程的影响无非就是乘上一个数值。(就好像初始钻头能力为 (w) ,实际上你可以按 (1) 来做,最后再把 (ans) 乘上 (w) )。
正难则反,(F[i]) 表示第 (i o n) 个星球的最优收入,且假设从第 (i) 个星球开始时钻头能力为(1)。
换句话说,这样的状态设计,规定了一个参考系。
转移过程就变得简单:如果在第 (i) 个星球开采,那么第 (i+1 o n) 个星球的初始钻头能力就是 (1*(1-0.01k)) 。
换句话说,就是 (F[i+1]*(1-0.01k)) 。
所以 (F[i]=max{F[i+1],F[i+1]*(1-0.01k)+a[i]})
对于维护型星球,大同小异。就系数和代价的正负而已。
观察方程,(F[i]=max{F[i+1],F[i+1]*(1-0.01k)+a[i]})
实际上就是取下 (i+1 o n) 的最值而已,所以这题实际上就成了贪心。
代码如下
#include <bits/stdc++.h>
#define MAXN 100007
using namespace std;
int n,w,t[MAXN],a[MAXN];
double k,c,ans;
int main() {
scanf("%d%lf%lf%d",&n,&k,&c,&w);
k=1-0.01*k;c=1+0.01*c;
for(int i=1;i<=n;i++) scanf("%d%d",&t[i],&a[i]);
for(int i=n;i;i--) {
if (t[i]==1) ans=max(ans,ans*k+a[i]);
else ans=max(ans,ans*c-a[i]);
}
printf("%.2lf
",ans*w);
}