zoukankan      html  css  js  c++  java
  • CPPU算法&编程协会2021寒假第二次模拟赛部分题解

    判断地址类型

    背景知识

    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 为钻头当前能力值)

    1. 资源型:含矿物质量 (a[i]) ,若选择开采,则得到 (a[i]*p) 的金钱,之后钻头损耗 (k%) ,即 (p=p*(1-0.01k))
    2. 维修型:维护费用 (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);
    }
    
  • 相关阅读:
    STM32 HAL库 UART 串口读写功能笔记
    c语言数组越界的避免方法
    MOS管做开关之初理解
    keil mdk 菜单 “project” 崩溃问题解决
    51 arm x86 的大小端记录
    (C99)复合字面量
    如何的keil试试调试中,看逻辑分析仪Logic viwer
    c语言之——整型的隐式转换与溢出检测
    C语言入坑指南-缓冲区溢出
    C语言入坑指南-被遗忘的初始化
  • 原文地址:https://www.cnblogs.com/zhwer/p/14412560.html
Copyright © 2011-2022 走看看