1. 云服务计费
编写一个程序为某云服务计算客户话单,输入为某云服务的计费日志和各种计费因子的计费单价的列表,计费日志内容包含时间戳、客户标识、计费因子、计费时长4个字段。日志中如果同一客户同一计费因子在相同时间戳上报多次话单只能计费一次,选先上报的日志计费。计算每个客户的话单总费用。
-
解答要求
时间限制: C/C++ 1000ms,其他语言: 2000ms内存限制: C/C++ 256MB,其他语言: 512MB
-
输入
第1行表示计费日志的条数n,是一个正整数,范围是
1<=n<=1000
第2到n+1行表示云服务的计费日志,共4列,第1列表示时间戳(是一个数字字符串,长度为10) 、第2列表示客户标识(是一个字符串,长度为1-16),第3列表示计费因子 (是一个字符串,长度为1-16,计费因子查不到时认为计费因子单价是0),第四列表示计费时长时长(范围为0-100,当计费时长不在范围内要认为是计费日志有问题,当成计费为0处理),这4个字段使用逗号分隔。
第n+2行表示计费因子的数量m,m是一个正整数,范围是1<=m<=100
第n+3到n+3+m行表示各种计费因子的计费单价的列表,该表有2列,第1列表示计费因子 (是一个字符串,长度为1-16),第2列表示单价(是一个正整数,范围为1~100),这2个字段使用逗号分
-
输出
每个客户的话单总费用,共2列,第1列表示客户名,第2列表示话单费用,2列用逗号分割,输出按客户标识字典序升序排序
cpp
input:
5
1627845600,client1,factorA,10
1627845605,client2,factorB,15
1627845610,client1,factorA,5
1627845610,client1,factorB,8
1627845620,client2,factorB,20
2
factorA,5
factorB,7
output:
client1,131
client2,245
思路:
- 首先对输入的字符串 计费日志 进行拆分,保存在 journal;
- 通过 set 进行判断是否存在相同的时间戳客户和计费因子,如果重复则不保存该记录;否则将该记录按照 clientID 和 factorID 、duration 保存在 mp;
- 对输入的字符串 计费因子的计费单价 进行拆分,以 unordered_map 形式保存在 factorValue;
- 根据 mp 和 factorValue 合并统计出各个用户的消费,保存在 ans 中,由于 ans 是 map,其中的元素会按照 key 升序排序。
- 遍历 ans ,输出最终结果。
cpp
#include<iostream>
#include<unordered_map>
#include<string>
#include<vector>
#include<map>
#include<unordered_set>
using namespace std;
int main (void) {
int n, m;
cin >> n;
// 用于保存每次输入的字段值
// journal[0] : time, journal[1] : clientID, journal[2] : factorID, journal[3] : duration
// factor[0] : factorID, Value
vector<string> journal(4), factor(2);
unordered_map<string, int> factorValue; // factorValue[factorID] = Value
unordered_set<string> uset; // 去重(时间戳+clientID+factorID)
map<pair<string, string>, int> mp; // mp[{clientID, factorID}] = duration;
map<string, int> ans; // 存放最终结果,map会按照key自动排序
string s; // 用于每次字符串输入
for (int i = 0; i < n; ++i) {
cin >> s;
int pos = 0; // 标记每次查找的起始位置
int j = 0;
// 遇到 , 就处理
while (pos < s.size()) {
int next = s.find(',', pos);
journal[j++] = s.substr(pos, next - pos);
pos = next + 1;
if (next == -1) break;
}
string time = journal[0];
string client = journal[1];
string factor = journal[2];
string duration = journal[3];
string ss = time + client + factor; // 判断是否重复的标识符
if (!uset.count(ss)) uset.insert(ss); // 没有重复
else continue; // 重复的话不保存该条记录
int dura = 0; // 将duration转化为int型,也可以使用stoi函数
for (int k = 0; k < duration.size(); ++k) {
dura *= 10;
dura += duration[k] - '0';
}
if (dura < 0 || dura > 100) continue; // dura不符合要求,不保存该记录
if (mp.find(make_pair(client, factor)) == mp.end()) { // 还没有存入该key
mp[make_pair(client, factor)] = dura;
}
else mp[make_pair(client, factor)] += dura; // 非第一次遇到,+=
}
// factorA, 5
cin >> m;
for (int i = 0; i < m; ++i) {
cin >> s;
int pos = 0;
int j = 0;
while (pos < s.size()) {
int next = s.find(',', pos);
factor[j++] = s.substr(pos, next - pos);
pos = next + 1;
if (next == -1) break;
}
factorValue[factor[0]] = stoi(factor[1]);
}
// 遍历mp,对每个用户进行统计
for (auto per : mp) {
// per.first.first: clientID
// per.first.second : factorID
// per.second : dura
if (ans.find(per.first.first) == ans.end()) {
// 还没有存入该用户
ans[per.first.first] = factorValue[per.first.second] * per.second;
}
else {
// 该用户已经存在
ans[per.first.first] += factorValue[per.first.second] * per.second;
}
}
// 输出最终结果
// map已经默认按照key值升序排序
for (auto a : ans) {
cout << a.first << "," << a.second << endl;
}
return 0;
}
2.相似图片分类
小明想要处理一批图片,将相似的图片分类。他首先对图片的特征采样,得到图片之间的相似度,然后按照以下规则判断图片是否可以归为一类:
-
相似度>0表示两张图片相似,
-
如果A和B相似,B和C相似,但A和C不相似。那么认为A和C间接相似,可以把ABC归为一类,但不计算AC的相似度
-
如果A和所有其他图片都不相似,则A自己归为一类,相似度为0.给定一个大小为NxN的矩阵M存储任意两张图片的相似度,M[i][j]即为第i个图片和第j个图片的相似度,请按照"从大到小"的顺序返回每个相似类中所有图片的相似度之和。
-
解答要求
时间限制: C/C++ 1000ms,其他语言: 2000ms内存限制: C/C++ 256MB,其他语言: 512MB
-
输入
第一行一个数N,代表矩阵M中有N个图片,下面跟着N行,每行有N列数据,空格分隔(为了显示整弃,空格可能为多个) 代表N个图片之间的相似度。
0<N<=900
0<=M[i][j]<=100,输入保证M[i][i] =0,M[i][j]=M[j][i]
-
输出
每个相似类的相似度之和。格式为:一行数字,分隔符为1个空格。
cpp
input:
5
0 0 50 0 0
0 0 0 25 0
50 0 0 0 15
0 25 0 0 0
0 0 15 0 0
output:
65 25
思路:
- 本题考察并查集,将相似度大于 0 的节点合并,用 score 数组储存分数,每次合并(join)的时候累加分数,分数累加在父元素上;
- 最后遍历 score 数组,找到父元素的分数,存入结果数组 ans,将其降序输出。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> father(905, 0);
vector<int> score(905, 0);
// 查找父元素
int find(int u) {
return father[u] == u ? u : father[u] = find(father[u]);
}
// 判断是否在同一集合
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 将二者加入同一集合
void join(int u, int v, int sc) {
u = find(u);
v = find(v);
// 由于二者肯定不在同一集合,所以不再判断
father[v] = u; // 指定父元素
score[u] += sc + score[v]; // 父元素的分数 += 二者相似度分数 + v元素原属集合的分数
}
int main() {
int n;
cin >> n;
vector<vector<int>> similar(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i) {
father[i] = i;
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> similar[i][j];
// 当相似分数大于0 且 二者不在同一集合(避免重复遍历) ,将其加入
if (similar[i][j] > 0 && !isSame(i, j)) join(i, j, similar[i][j]);
}
}
vector<int> ans;
for (int i = 0; i < n; ++i) {
// 由于父元素的分数才是最终分数,所以找到父元素
if (father[i] == i) {
ans.push_back(score[i]);
}
}
sort(ans.begin(), ans.end(), greater<int>()); // 降序排序
for (int i : ans) cout << i << " "; // 输出答案
return 0;
}
3.网络保卫战
公有云的某个region内,N个网络节点组网情况可以使用一个n* n的矩阵matrix表示,在这个组网图中,matrix[i][j] = p 时,表示用户在编号为 i的节点访问编号为j的节点时,必须在 i节点上具有>=p 的权限等级(p=0时表示无法通过第i节点访问j节点),如果用户成功访问了j节点,那么它在j节点上的权限等级调整为 P。
exposed 为一个整数数组,表示暴露在公网上的网络节点的编号列表。某天扫描发现这批暴需在公网的节点存在被外部恶意攻击风险且该攻击会影响到可访问的其他节点,并可以持续传递进行攻击,被恶意攻击的节点从公网访问时,攻击者获得了ROOT 权限(权限等级为10,即最大值)。
小李是一名网络安全工程师,为了在有限的时间内尽可能的减少故障带来的损失,需要立即将某个节点从公网"下线"。
假设攻击结束时,被攻击过的节点数量为R,请帮小李计算出将哪个节点下线能使R尽可能小,如果答案有多个节点,返回索引最小的那个节点。
请注意:从公网"下线"的节点,不会受到来自公网的攻击,但仍然可能被"可访问"的其他节点传递攻击。
-
解答要求
时间限制: C/C++ 5000ms,其他语言: 10000ms内存限制: C/C++ 128MB,其他语言: 256MB
-
输入
输入的第一行是网络节点数量N
后续的N行,每行N个数字v,以空格分割,形成一个N*N的矩阵,表示网络节点组网的矩阵。
最后一行,输入 exposed 数组,表示暴露在公网上的网络节点的编号列表,数组元素不会重复。
cpp2 <= n <= 24 0 <= v <= 10 0 <= exposed[i] <= n-1
-
输出
exposed 数组中,计划"下线"的那个节点的编号。
cpp
input:
4
1 0 0 0
0 1 2 0
0 1 1 4
0 0 3 1
1 3
output:
3
思路:
本题数据范围小, 直接模拟暴力枚举就行。遍历每个暴露的节点,dfs搜索遍历能走到多少节点, 选择能到达节点数最多的点就是要下线的答案。