
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++/Go)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。
一、题目描述
在云服务中,有 n 个安全策略,编号 1 到 n。每个策略有一个重要度权重(正整数)。某些策略对之间互斥,不能同时启用。
现在需要为某个实例恰好选择 k 个策略,要求:
-
选中的策略之间没有互斥关系(即构成一个独立集);
-
在满足条件1的所有大小为 k 的策略组合中,使得选中策略的权重之和最大(权重之和就是重要度权重数组中的权重的和)。
请返回所有满足上述条件的最优策略组合(即权重和最大的所有合法组合)。
二、输入描述
• 第一行:整数 n(策略总数)
• 第二行:整数 k(需要选择的策略数量)
• 第三行:长度为 n 的整数数组 weights,其中 weights[i] 表示策略 i + 1 的权重,字符串表示通过 , 分割
• 第四行:二维整数数组 conflicts,每个元素为 [a, b],表示策略 a 和 b 互斥(无向,无重复边),通过字符串表示,a和b之间通过 , 分割,不同组通过空格分割
数据规模:
• 1 ≤ n ≤ 25
• 0 ≤ k ≤ n
• 1 ≤ weights[i] ≤ 1000
• 0 ≤ conflicts.length ≤ n(n − 1)/2
三、输出描述
• 每个组合内的策略编号按升序排列;
• 所有组合按字典序排列(将每个组合视为一个数字序列);
• 如果没有合法组合(例如不存在大小为 k 的独立集),则返回空数组 []。
注意
• 如果没有大小为 k 的独立集,则返回 空
四、测试用例
测试用例1:
1、输入
4
2
5,1,3,4
1,2 2,3
2、输出
1,4
3、说明
合法的两策略组合中,1,4 的权重为 5+4=9,为唯一最大值。
测试用例2:
1、输入
5
3
3,4,3,4,3
1,3 2,4 3,5
2、输出
1,2,5 1,4,5
3、说明
1,2,5 和 1,4,5 均不存在内部冲突,权重都为 10,并且已经按照字典序输出。
五、解题思路
把 n 个安全策略看成图中的 n 个顶点,互斥关系看成无向边,题目实际要求寻找“大小恰好为 k 的最大权独立集”,并输出所有权重和相同的最优方案。
由于 n 最大只有 25,可以采用 DFS 回溯枚举组合。为了快速判断新选择的策略是否与已经选择的策略冲突,使用一个整数位掩码表示当前已选择策略,同时为每个策略维护 conflictMask:若 conflictMask[i] 的第 j 位为 1,说明策略 i 与策略 j 互斥。判断能否选择策略 i 只需执行 conflictMask[i] & selectedMask,复杂度为 O(1)。
单纯回溯最坏情况下需要枚举大量组合,因此增加一个 DP 乐观上界 upper[i][c]:表示从策略 i 到 n-1 中暂时忽略互斥关系、恰好再选 c 个策略时能够获得的最大权重。忽略冲突后的结果一定不小于真实可行结果,因此如果“当前权重 + upper”仍小于已经找到的最优权重,该分支一定不可能产生最优解,可以安全剪枝。
算法执行两遍 DFS。第一遍只计算最大权重 bestSum,不保存方案;第二遍再次按策略编号从小到大搜索,只输出权重恰好等于 bestSum 的方案。由于 DFS 始终按编号递增选择,所以每个组合内部天然升序,所有组合也天然按照字典序输出,无需额外排序。
预处理 upper 的复杂度为 O(nk),搜索最坏仍可能达到 O(2^n),这是一般图最大权独立集问题本身的性质;但 n≤25,并结合选取数量限制、冲突判断和上界剪枝后具有较好的实际性能。除递归路径和 upper 外不保存全部答案,额外空间约为 O(nk+n)。
六、Python算法源码
import sys
lines = sys.stdin.read().splitlines()
n = int(lines[0].strip())
k = int(lines[1].strip())
weights = list(map(int, lines[2].strip().split(',')))
conflict_line = lines[3].strip() if len(lines) > 3 else ''
# conflict_mask[i] 的第 j 位为 1,
# 表示策略 i 与策略 j 存在互斥关系。
conflict_mask = [0] * n
if conflict_line:
for pair in conflict_line.split():
a, b = map(int, pair.split(','))
a -= 1
b -= 1
# 互斥关系为无向关系,两个方向均需要记录
conflict_mask[a] |= 1 << b
conflict_mask[b] |= 1 << a
NEG = -10 ** 30
# upper[i][need]:
# 从 i 到 n-1 中忽略互斥限制,恰好选 need 个策略的最大权重。
upper = [[NEG] * (k + 1) for _ in range(n + 1)]
upper[n][0] = 0
# 使用类似 0/1 背包的方式计算 DFS 的乐观上界
for i in range(n - 1, -1, -1):
upper[i][0] = 0
for need in range(1, k + 1):
# 不选择当前策略
skip = upper[i + 1][need]
# 选择当前策略
if upper[i + 1][need - 1] == NEG:
take = NEG
else:
take = weights[i] + upper[i + 1][need - 1]
upper[i][need] = max(skip, take)
best_sum = NEG
def dfs_best(start, need, selected_mask, total):
"""
第一遍 DFS:
只寻找最大权重,不保存具体组合。
"""
global best_sum
if need == 0:
best_sum = max(best_sum, total)
return
# 剩余策略数量不足
if n - start < need:
return
# 即使忽略所有冲突也无法达到当前最优值,可以安全剪枝
if (
upper[start][need] == NEG
or (
best_sum != NEG
and total + upper[start][need] < best_sum
)
):
return
for i in range(start, n - need + 1):
# 位与非 0,说明当前策略与已选择策略存在冲突
if conflict_mask[i] & selected_mask:
continue
dfs_best(
i + 1,
need - 1,
selected_mask | (1 << i),
total + weights[i]
)
dfs_best(0, k, 0, 0)
if best_sum == NEG or k == 0:
print('[]')
sys.exit(0)
path = []
first_output = True
out = sys.stdout
def dfs_print(start, need, selected_mask, total):
"""
第二遍 DFS:
只输出权重等于 best_sum 的组合。
"""
global first_output
if need == 0:
if total == best_sum:
if not first_output:
out.write(' ')
first_output = False
out.write(','.join(map(str, path)))
return
if n - start < need:
return
# 理论最大值都无法达到 best_sum,直接剪枝
if (
upper[start][need] == NEG
or total + upper[start][need] < best_sum
):
return
# 从小到大选择策略,因此结果天然满足字典序
for i in range(start, n - need + 1):
if conflict_mask[i] & selected_mask:
continue
path.append(i + 1)
dfs_print(
i + 1,
need - 1,
selected_mask | (1 << i),
total + weights[i]
)
path.pop()
dfs_print(0, k, 0, 0)
out.write('\n')
七、JavaScript算法源码
const fs = require('fs');
const lines = fs
.readFileSync(0, 'utf8')
.split(/\r?\n/);
const n = Number(lines[0].trim());
const k = Number(lines[1].trim());
const weights = lines[2]
.trim()
.split(',')
.map(Number);
const conflictLine = (lines[3] || '').trim();
/*
* n <= 25。
* JavaScript 位运算使用 32 位整数,
* bit 0 ~ bit 24 足够表示全部策略。
*/
const conflictMask = new Array(n).fill(0);
if (conflictLine.length > 0) {
for (const pair of conflictLine.split(/\s+/)) {
let [a, b] = pair.split(',').map(Number);
a--;
b--;
// 互斥关系是无向的
conflictMask[a] |= (1 << b);
conflictMask[b] |= (1 << a);
}
}
const NEG = Number.NEGATIVE_INFINITY;
/*
* upper[i][need]:
* 忽略冲突时,从 i 开始恰好选择 need 个策略
* 可以获得的最大权重。
*/
const upper = Array.from(
{ length: n + 1 },
() => new Array(k + 1).fill(NEG)
);
upper[n][0] = 0;
// 0/1 DP 计算乐观上界
for (let i = n - 1; i >= 0; i--) {
upper[i][0] = 0;
for (let need = 1; need <= k; need++) {
const skip = upper[i + 1][need];
const take =
upper[i + 1][need - 1] === NEG
? NEG
: weights[i]
+ upper[i + 1][need - 1];
upper[i][need] = Math.max(skip, take);
}
}
let bestSum = NEG;
/*
* 第一遍 DFS:
* 只求最大权重 bestSum。
*/
function dfsBest(start, need, selectedMask, sum) {
if (need === 0) {
bestSum = Math.max(bestSum, sum);
return;
}
if (n - start < need) {
return;
}
/*
* 忽略冲突之后的理论最大值仍低于 bestSum,
* 当前分支一定不可能成为最优解。
*/
if (
upper[start][need] === NEG
|| (
bestSum !== NEG
&& sum + upper[start][need] < bestSum
)
) {
return;
}
for (let i = start; i <= n - need; i++) {
// 与已经选择的策略存在冲突
if ((conflictMask[i] & selectedMask) !== 0) {
continue;
}
dfsBest(
i + 1,
need - 1,
selectedMask | (1 << i),
sum + weights[i]
);
}
}
dfsBest(0, k, 0, 0);
if (bestSum === NEG || k === 0) {
console.log('[]');
process.exit(0);
}
const path = [];
let firstOutput = true;
/*
* 第二遍 DFS:
* 只输出权重恰好等于 bestSum 的方案。
*/
function dfsPrint(start, need, selectedMask, sum) {
if (need === 0) {
if (sum === bestSum) {
if (!firstOutput) {
process.stdout.write(' ');
}
firstOutput = false;
process.stdout.write(
path.join(',')
);
}
return;
}
if (n - start < need) {
return;
}
// 理论上界无法达到 bestSum
if (
upper[start][need] === NEG
|| sum + upper[start][need] < bestSum
) {
return;
}
/*
* 按策略编号从小到大枚举,
* 因而无需额外进行字典序排序。
*/
for (let i = start; i <= n - need; i++) {
if ((conflictMask[i] & selectedMask) !== 0) {
continue;
}
path.push(i + 1);
dfsPrint(
i + 1,
need - 1,
selectedMask | (1 << i),
sum + weights[i]
);
path.pop();
}
}
dfsPrint(0, k, 0, 0);
process.stdout.write('\n');
八、C算法源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#define MAXN 25
#define LINE_SIZE 8192
int n, k;
int weights[MAXN];
/*
* conflictMask[i]:
* 用二进制位保存与策略 i 互斥的所有策略。
*/
uint32_t conflictMask[MAXN];
long long upper[MAXN + 1][MAXN + 1];
long long bestSum;
int path[MAXN];
int pathLen = 0;
int firstOutput = 1;
const long long NEG = LLONG_MIN / 4;
/*
* 第一遍 DFS:
* 只计算最大的权重和。
*/
void dfsBest(
int start,
int need,
uint32_t selectedMask,
long long sum
) {
if (need == 0) {
if (sum > bestSum) {
bestSum = sum;
}
return;
}
// 剩余策略不够选择
if (n - start < need) {
return;
}
/*
* upper 是忽略冲突后的乐观上界。
* 如果连理论最大值都达不到 bestSum,
* 当前分支可以直接舍弃。
*/
if (
upper[start][need] == NEG
|| (
bestSum != NEG
&& sum + upper[start][need] < bestSum
)
) {
return;
}
for (int i = start; i <= n - need; ++i) {
/*
* 位与结果非 0,
* 表示策略 i 与某个已选策略互斥。
*/
if ((conflictMask[i] & selectedMask) != 0) {
continue;
}
dfsBest(
i + 1,
need - 1,
selectedMask | (1u << i),
sum + weights[i]
);
}
}
/*
* 第二遍 DFS:
* 输出所有权重等于 bestSum 的组合。
*/
void dfsPrint(
int start,
int need,
uint32_t selectedMask,
long long sum
) {
if (need == 0) {
if (sum == bestSum) {
if (!firstOutput) {
putchar(' ');
}
firstOutput = 0;
for (int i = 0; i < pathLen; ++i) {
if (i > 0) {
putchar(',');
}
printf("%d", path[i]);
}
}
return;
}
if (n - start < need) {
return;
}
/*
* 理论上限都不足 bestSum,
* 说明这个分支不可能产生最优方案。
*/
if (
upper[start][need] == NEG
|| sum + upper[start][need] < bestSum
) {
return;
}
/*
* 按编号递增回溯,
* 因此最终输出天然满足字典序。
*/
for (int i = start; i <= n - need; ++i) {
if ((conflictMask[i] & selectedMask) != 0) {
continue;
}
path[pathLen++] = i + 1;
dfsPrint(
i + 1,
need - 1,
selectedMask | (1u << i),
sum + weights[i]
);
--pathLen;
}
}
int main(void) {
char line[LINE_SIZE];
// 第一行:n
fgets(line, sizeof(line), stdin);
n = atoi(line);
// 第二行:k
fgets(line, sizeof(line), stdin);
k = atoi(line);
// 第三行:weights
fgets(line, sizeof(line), stdin);
char *token = strtok(
line,
",\r\n"
);
for (
int i = 0;
i < n && token != NULL;
++i
) {
weights[i] = atoi(token);
token = strtok(
NULL,
",\r\n"
);
}
memset(
conflictMask,
0,
sizeof(conflictMask)
);
/*
* 第四行 conflicts 可能为空。
*/
line[0] = '\0';
if (
fgets(
line,
sizeof(line),
stdin
) != NULL
) {
token = strtok(
line,
" \t\r\n"
);
while (token != NULL) {
int a, b;
if (
sscanf(
token,
"%d,%d",
&a,
&b
) == 2
) {
--a;
--b;
// 无向互斥关系
conflictMask[a] |=
(1u << b);
conflictMask[b] |=
(1u << a);
}
token = strtok(
NULL,
" \t\r\n"
);
}
}
/*
* 初始化 upper。
*/
for (int i = 0; i <= n; ++i) {
for (int c = 0; c <= k; ++c) {
upper[i][c] = NEG;
}
}
upper[n][0] = 0;
/*
* 0/1 DP:
* 忽略冲突,求后缀恰好选择 need 个策略时
* 最大能够获得的权重。
*/
for (int i = n - 1; i >= 0; --i) {
upper[i][0] = 0;
for (
int need = 1;
need <= k;
++need
) {
long long skip =
upper[i + 1][need];
long long take;
if (
upper[i + 1][need - 1]
== NEG
) {
take = NEG;
} else {
take =
weights[i]
+ upper[i + 1][need - 1];
}
upper[i][need] =
skip > take
? skip
: take;
}
}
bestSum = NEG;
// 第一遍求最大权重
dfsBest(
0,
k,
0u,
0
);
if (
bestSum == NEG
|| k == 0
) {
printf("[]\n");
return 0;
}
// 第二遍输出全部最优组合
dfsPrint(
0,
k,
0u,
0
);
putchar('\n');
return 0;
}
九、C++算法源码
#include <bits/stdc++.h>
using namespace std;
int n, k;
vector<int> weights;
/*
* conflictMask[i] 的二进制位表示
* 与策略 i 互斥的策略集合。
*/
vector<uint32_t> conflictMask;
vector<vector<long long>> upper;
long long bestSum;
const long long NEG = LLONG_MIN / 4;
vector<int> path;
bool firstOutput = true;
/*
* 第一遍 DFS,只求最大权重。
*/
void dfsBest(
int start,
int need,
uint32_t selectedMask,
long long sum
) {
if (need == 0) {
bestSum = max(
bestSum,
sum
);
return;
}
if (n - start < need) {
return;
}
/*
* 忽略冲突后的理论最大值仍然小于当前最优值,
* 则该分支没有继续搜索的必要。
*/
if (
upper[start][need] == NEG
|| (
bestSum != NEG
&& sum + upper[start][need]
< bestSum
)
) {
return;
}
for (
int i = start;
i <= n - need;
++i
) {
// 当前策略与已选择策略存在互斥
if (
(conflictMask[i] & selectedMask)
!= 0
) {
continue;
}
dfsBest(
i + 1,
need - 1,
selectedMask | (1u << i),
sum + weights[i]
);
}
}
/*
* 第二遍 DFS,只输出达到 bestSum 的组合。
*/
void dfsPrint(
int start,
int need,
uint32_t selectedMask,
long long sum
) {
if (need == 0) {
if (sum == bestSum) {
if (!firstOutput) {
cout << ' ';
}
firstOutput = false;
for (
int i = 0;
i < (int) path.size();
++i
) {
if (i > 0) {
cout << ',';
}
cout << path[i];
}
}
return;
}
if (n - start < need) {
return;
}
/*
* 第二遍已经知道目标 bestSum,
* 连理论上限都达不到目标时直接剪枝。
*/
if (
upper[start][need] == NEG
|| sum + upper[start][need]
< bestSum
) {
return;
}
/*
* 策略始终按编号升序加入 path,
* 所有答案天然满足题目的字典序要求。
*/
for (
int i = start;
i <= n - need;
++i
) {
if (
(conflictMask[i] & selectedMask)
!= 0
) {
continue;
}
path.push_back(i + 1);
dfsPrint(
i + 1,
need - 1,
selectedMask | (1u << i),
sum + weights[i]
);
path.pop_back();
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string line;
// n
getline(cin, line);
n = stoi(line);
// k
getline(cin, line);
k = stoi(line);
// weights
getline(cin, line);
weights.resize(n);
stringstream ws(line);
string part;
for (int i = 0; i < n; ++i) {
getline(
ws,
part,
','
);
weights[i] = stoi(part);
}
conflictMask.assign(
n,
0
);
// conflicts 这一行可能为空
if (
getline(cin, line)
&& !line.empty()
) {
stringstream cs(line);
string pair;
while (cs >> pair) {
size_t pos =
pair.find(',');
int a =
stoi(
pair.substr(
0,
pos
)
) - 1;
int b =
stoi(
pair.substr(
pos + 1
)
) - 1;
// 无向互斥关系
conflictMask[a] |=
(1u << b);
conflictMask[b] |=
(1u << a);
}
}
/*
* 初始化 upper。
*/
upper.assign(
n + 1,
vector<long long>(
k + 1,
NEG
)
);
upper[n][0] = 0;
/*
* 预处理忽略冲突情况下的最大后缀权重,
* 作为回溯过程中的乐观上界。
*/
for (int i = n - 1; i >= 0; --i) {
upper[i][0] = 0;
for (
int need = 1;
need <= k;
++need
) {
long long skip =
upper[i + 1][need];
long long take =
upper[i + 1][need - 1]
== NEG
? NEG
: weights[i]
+ upper[i + 1][need - 1];
upper[i][need] =
max(
skip,
take
);
}
}
bestSum = NEG;
// 第一遍:计算最大权重
dfsBest(
0,
k,
0u,
0
);
if (
bestSum == NEG
|| k == 0
) {
cout << "[]\n";
return 0;
}
// 第二遍:输出全部最优组合
dfsPrint(
0,
k,
0u,
0
);
cout << '\n';
return 0;
}
十、Go算法源码
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
const NEG int64 = -1 << 60
var (
n int
k int
weights []int64
// conflictMask[i] 使用二进制位保存
// 与策略 i 存在互斥关系的所有策略。
conflictMask []uint32
// upper[i][need] 是忽略冲突后的乐观权重上界。
upper [][]int64
bestSum int64 = NEG
path []int
firstOutput = true
writer *bufio.Writer
)
/*
第一遍 DFS:
只寻找最大权重 bestSum。
*/
func dfsBest(
start int,
need int,
selectedMask uint32,
sum int64,
) {
if need == 0 {
if sum > bestSum {
bestSum = sum
}
return
}
// 剩余策略数量不足
if n-start < need {
return
}
/*
upper 忽略了所有互斥关系,
因此是当前分支可能取得权重的乐观上界。
如果连理论最大值都低于 bestSum,
该分支必然无法产生更优方案。
*/
if upper[start][need] == NEG ||
(bestSum != NEG &&
sum+upper[start][need] < bestSum) {
return
}
for i := start; i <= n-need; i++ {
/*
位与非 0,
说明当前策略与某个已选择策略存在冲突。
*/
if conflictMask[i]&selectedMask != 0 {
continue
}
dfsBest(
i+1,
need-1,
selectedMask|(uint32(1)<<i),
sum+weights[i],
)
}
}
/*
第二遍 DFS:
只输出权重等于 bestSum 的组合。
*/
func dfsPrint(
start int,
need int,
selectedMask uint32,
sum int64,
) {
if need == 0 {
if sum == bestSum {
if !firstOutput {
fmt.Fprint(
writer,
" ",
)
}
firstOutput = false
for i, v := range path {
if i > 0 {
fmt.Fprint(
writer,
",",
)
}
fmt.Fprint(
writer,
v,
)
}
}
return
}
if n-start < need {
return
}
/*
第二遍已经知道最终目标 bestSum,
理论上界都无法达到 bestSum 时直接剪枝。
*/
if upper[start][need] == NEG ||
sum+upper[start][need] < bestSum {
return
}
/*
按策略编号递增选择,
因此组合内部和组合之间都天然满足字典序。
*/
for i := start; i <= n-need; i++ {
if conflictMask[i]&selectedMask != 0 {
continue
}
path = append(
path,
i+1,
)
dfsPrint(
i+1,
need-1,
selectedMask|(uint32(1)<<i),
sum+weights[i],
)
// 回溯,删除刚加入的策略
path = path[:len(path)-1]
}
}
func main() {
scanner :=
bufio.NewScanner(os.Stdin)
// 第一行 n
scanner.Scan()
n, _ =
strconv.Atoi(
strings.TrimSpace(
scanner.Text(),
),
)
// 第二行 k
scanner.Scan()
k, _ =
strconv.Atoi(
strings.TrimSpace(
scanner.Text(),
),
)
// 第三行 weights
scanner.Scan()
weightParts :=
strings.Split(
strings.TrimSpace(
scanner.Text(),
),
",",
)
weights =
make(
[]int64,
n,
)
for i := 0; i < n; i++ {
v, _ :=
strconv.Atoi(
strings.TrimSpace(
weightParts[i],
),
)
weights[i] =
int64(v)
}
/*
解析互斥关系。
第四行允许为空。
*/
conflictMask =
make(
[]uint32,
n,
)
conflictLine := ""
if scanner.Scan() {
conflictLine =
strings.TrimSpace(
scanner.Text(),
)
}
if conflictLine != "" {
for _, pair :=
range strings.Fields(
conflictLine,
) {
ab :=
strings.Split(
pair,
",",
)
a, _ :=
strconv.Atoi(
strings.TrimSpace(
ab[0],
),
)
b, _ :=
strconv.Atoi(
strings.TrimSpace(
ab[1],
),
)
a--
b--
// 互斥关系是无向的
conflictMask[a] |=
uint32(1) << b
conflictMask[b] |=
uint32(1) << a
}
}
/*
初始化 upper 数组。
*/
upper =
make(
[][]int64,
n+1,
)
for i := range upper {
upper[i] =
make(
[]int64,
k+1,
)
for c := 0; c <= k; c++ {
upper[i][c] = NEG
}
}
upper[n][0] = 0
/*
类似 0/1 背包:
忽略策略之间的冲突,
计算从后缀中恰好选择 need 个策略时
最大的权重和。
该值用于 DFS 的安全剪枝。
*/
for i := n - 1; i >= 0; i-- {
upper[i][0] = 0
for need := 1; need <= k; need++ {
skip :=
upper[i+1][need]
take := NEG
if upper[i+1][need-1] != NEG {
take =
weights[i] +
upper[i+1][need-1]
}
if skip > take {
upper[i][need] =
skip
} else {
upper[i][need] =
take
}
}
}
// 第一遍:求最大权重
dfsBest(
0,
k,
0,
0,
)
writer =
bufio.NewWriter(
os.Stdout,
)
defer writer.Flush()
if bestSum == NEG || k == 0 {
fmt.Fprintln(
writer,
"[]",
)
return
}
// 第二遍:输出全部最优组合
dfsPrint(
0,
k,
0,
0,
)
fmt.Fprintln(writer)
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 新系统 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++/Go)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

324



被折叠的 条评论
为什么被折叠?



