#P2335. 第3题-公网下线方案
-
1000ms
Tried: 1489
Accepted: 163
Difficulty: 7
所属公司 :
华为
时间 :2024年4月10日-暑期实习
-
算法标签>BFS
第3题-公网下线方案
题解思路与方法
建模
- 用一个 N×N 的邻接矩阵
matrix表示节点间的访问授权门槛:matrix[i][j]=p(p>0)表示从节点 i 到节点 j 需要在节点 i 上拥有至少 p 级权限;matrix[i][j]=0表示不可访问。
exposed数组为所有直接暴露在公网的节点集合。若不下线某节点 x,攻击者可从所有其他暴露节点(初始权限为 10)发起多源 BFS/DFS,对可达节点进行“感染”。
算法
- 枚举下线节点 r∈exposed:
- 初始队列为所有
exposed中除去r的节点,每个节点在队列中的初始权限为 10; - 对每个出队状态 (u,permu) 遍历所有目标节点 v:
- 若
matrix[u][v]>0且perm_u >= matrix[u][v],则可访问; - 到达 v 后,其权限变为
perm_v = matrix[u][v]; - 若之前到达 v 时的权限小于
perm_v,则更新并将 (v,permv) 入队。
- 若
- BFS 结束后,统计所有被访问过的节点数 R。
- 初始队列为所有
- 在所有候选 r 中,选使 R 最小的节点,若多个,取编号最小者。
这种做法相当于对带状态的图做多源最优 BFS,状态数上限为 N×11(权限等级 0–10)。
复杂度分析
- 枚举下线节点:最多 ∣exposed∣≤N 次;
- 每次 BFS 状态空间最多 O(N×P),其中 P=11 为权限等级种类;
- 每个状态遍历 N 条边,故单次 BFS 复杂度 O(N×P×N)=O(N2)(常数 P 可忽略);
- 总体时间复杂度 O(N3),在 N≤24 时远可接受。
代码
C++
#include <bits/stdc++.h>
using namespace std;
int a[40][40]; // 存储权限矩阵
int n, m = 1; // n 为节点数量,m 为暴露节点的计数(从 1 开始)
int e[40]; // 存储暴露节点的编号
int vis[40]; // 记录到达每个节点的最高权限,初始为 -1
// bfs 模拟攻击过程,返回“未被攻击”(安全)节点数
int bfs(int x) {
queue<pair<int,int>> q;
// 初始化 vis 为 -1(未被攻击)
for(int i = 0; i < n; i++) {
vis[i] = -1;
}
// 多源入队:除去下线节点 x
for(int i = 1; i <= m; i++) {
if(i == x) continue;
vis[e[i]] = 10; // ROOT 权限
q.push({e[i], 10});
}
// 带状态 BFS
while(!q.empty()) {
auto [u, p] = q.front(); q.pop();
if(p < vis[u]) continue; // 已有更高权限到达过 u
for(int j = 0; j < n; j++) {
int req = a[u][j];
if(req > 0 && p >= req && vis[j] < req) {
vis[j] = req;
q.push({j, req});
}
}
}
// 统计未被攻击的节点数
int res = 0;
for(int i = 0; i < n; i++) {
if(vis[i] < 0) res++;
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
cin >> a[i][j];
// 读取暴露节点列表
while(cin >> e[m]) m++;
m--;
sort(e + 1, e + m + 1);
int mx = -1, id = 1;
for(int i = 1; i <= m; i++) {
int now = bfs(i);
if(now > mx) {
mx = now;
id = i;
}
}
cout << e[id] << "\n";
return 0;
}
Java
import java.util.*;
public class Main {
static int[][] a = new int[40][40];
static int n, m = 1; // m 从 1 开始计数
static int[] e = new int[40];
static int[] vis = new int[40]; // 记录到达每个节点的最高权限,初始为 -1
// bfs 模拟攻击,返回安全节点数
static int bfs(int x) {
Queue<int[]> q = new ArrayDeque<>();
// 初始化
for (int i = 0; i < n; i++) vis[i] = -1;
// 多源入队
for (int i = 1; i <= m; i++) {
if (i == x) continue;
vis[e[i]] = 10;
q.offer(new int[]{e[i], 10});
}
// 带状态 BFS
while (!q.isEmpty()) {
int[] cur = q.poll();
int u = cur[0], p = cur[1];
if (p < vis[u]) continue;
for (int v = 0; v < n; v++) {
int req = a[u][v];
if (req > 0 && p >= req && vis[v] < req) {
vis[v] = req;
q.offer(new int[]{v, req});
}
}
}
// 统计未被攻击节点数
int res = 0;
for (int i = 0; i < n; i++) {
if (vis[i] < 0) res++;
}
return res;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
a[i][j] = sc.nextInt();
// 读取暴露节点
while (sc.hasNextInt()) {
e[m++] = sc.nextInt();
}
m--;
Arrays.sort(e, 1, m + 1);
int mx = -1, id = 1;
for (int i = 1; i <= m; i++) {
int now = bfs(i);
if (now > mx) {
mx = now;
id = i;
}
}
System.out.println(e[id]);
}
}
Python
import queue
# 输入
n = int(input())
a = [list(map(int, input().split())) for _ in range(n)]
e = list(map(int, input().split()))
e.sort()
m = len(e)
# bfs 模拟攻击,返回安全节点数
def bfs(x):
q = queue.Queue()
# vis 用作 “到达最高权限”,初始为 -1
vis = [-1] * n
for i in range(m):
if i == x: continue
vis[e[i]] = 10
q.put((e[i], 10))
while not q.empty():
u, p = q.get()
if p < vis[u]:
continue
for j in range(n):
req = a[u][j]
if req > 0 and p >= req and vis[j] < req:
vis[j] = req
q.put((j, req))
# 未被攻击的节点数
return sum(1 for v in vis if v < 0)
mx = -1
idx = 0
for i in range(m):
now = bfs(i)
if now > mx:
mx = now
idx = i
print(e[idx])
javascript
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
// 读入节点数
let n = parseInt(await readline());
// 初始化权限矩阵 a[n][n]
let a = Array.from({ length: n }, () => Array(n).fill(0));
for (let i = 0; i < n; i++) {
let row = (await readline()).split(/\s+/).map(Number);
for (let j = 0; j < n; j++) {
a[i][j] = row[j];
}
}
// 读入并排序暴露节点列表 e
let e = (await readline()).trim().split(/\s+/).map(Number).sort((x, y) => x - y);
let m = e.length;
// vis[i] 存储到达 i 时的最高权限,初始为 -1(未被攻击)
let vis = Array(n).fill(-1);
// 模拟下线 e[x] 后的 BFS 攻击,返回安全节点数
function bfs(x) {
// 重置 vis
vis.fill(-1);
let q = [];
// 多源初始化,除去下线节点 x
for (let i = 0; i < m; i++) {
if (i === x) continue;
vis[e[i]] = 10; // ROOT 权限
q.push([e[i], 10]);
}
// 带状态 BFS
while (q.length) {
let [u, p] = q.shift();
// 如果当前权限不是最高,则跳过
if (p < vis[u]) continue;
// 遍历所有邻接节点
for (let j = 0; j < n; j++) {
let req = a[u][j];
if (req > 0 && p >= req && vis[j] < req) {
vis[j] = req;
q.push([j, req]);
}
}
}
// 统计安全(未被攻击)节点数
return vis.reduce((cnt, v) => cnt + (v < 0 ? 1 : 0), 0);
}
// 枚举每个暴露节点下线,选安全节点数最多者
let mx = -1, best = 0;
for (let i = 0; i < m; i++) {
let safeCnt = bfs(i);
if (safeCnt > mx) {
mx = safeCnt;
best = i;
}
}
console.log(e[best]);
rl.close();
})();
题目描述
公有云的某个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尽可能小,如果答案有多个节点,返回索引最小的那个节点。请注意:从公网“下线”的节点,不会受到来自公网的攻击,但仍然可能被“可访问"的其他节点传递攻击。
输入描述
输入的第一行是网络节点数量N 后续的N行,每行N个数字
v,以空格分割,形成一个N×N的矩阵,表示网络节点组网的矩阵。 最后一行,输入exposed数组,表示暴露在公网上的网络节点的编号列表数组元素不会重复。2≤N≤24
0≤v≤10
0≤exposed[i]≤N−1
输出描述
输出在 exposed 数组中,计划“下线"的那个节点的编号。
样例1
输入
4
1 0 0 0
0 1 2 0
0 1 1 4
0 0 3 1
1 3
输出
3
说明
1,3是公网暴露的节点
1,2,3三个节点是连通的,但相互访问需要考虑权限等级限制,例如从1节点登录,访问到2节点后,权限等级不足以访问3号节点
如果将1号节点从公网下线,那3号节点可以先访问2号在访问1号,此时R的值为3。如果将3号节点从公网下线,则只能通过1号节点访问到2号节点,而2号节点无法再访问3号节点,此时R的值为2,答案选择R值更小的公网节点下线方案,因此答案为3.
Related
In following contests: