题目链接
题目描述
在坐标轴的整数点 上给出
条闭区间线段,第
条线段用其端点
描述。
现在要从这 条线段中选择若干条,使得每个整数点被至少两条所选线段覆盖。求满足条件的选择方案数量。
两种方案视为不同,当且仅当存在某条线段在两方案中的“选不选”状态不同。答案对 取模。
解题思路
本题要求我们从 条线段中选出若干条,使得
中的每个整数点都被至少两条线段覆盖,并计算满足条件的方案总数。
这是一个组合计数问题。由于 的范围通常不大(例如
),我们可以考虑使用搜索算法来遍历所有可能的线段选择组合。一个朴素的方案是枚举
种选择,然后对每一种选择,都检查是否满足覆盖条件。但每次检查都需要
的时间,总复杂度太高。
为了优化检查过程,我们可以采用 扫描线 (Sweep-line) 算法进行预处理,并结合 回溯搜索 (Backtracking) 进行剪枝。
1. 扫描线预处理
扫描线的核心思想是将二维的区间问题转化为一维的事件点问题。
- 事件点: 对于每条线段
,我们将其拆分为两个事件:在坐标
处有一个“线段开始”事件,在坐标
处有一个“线段结束”事件。
- 排序: 我们将所有
个事件点按坐标从小到大排序。
- 离散化与分段: 排序后的事件点会将坐标轴
分割成一系列“基本区间”。在任意一个基本区间内部(不含端点),覆盖它的线段数量是恒定的。
- 信息统计: 我们可以通过一次遍历(扫描)这些事件点,来高效地计算出每个基本区间的覆盖数,以及每个事件点本身的覆盖情况。我们将这些信息存储在一个数组
coverage_info
中,其中每个元素记录[坐标点, 到下一个坐标点之间的覆盖数, 在此坐标点开始的线段数, 在此坐标点结束的线段数]
。
2. 回溯搜索 (DFS)
在预处理得到所有线段都选择时,整个坐标轴的覆盖信息后,我们就可以开始进行回溯搜索。代码采用了一种巧妙的逆向思路:
- 初始状态: 假设我们选择了全部
条线段。利用扫描线算法计算出这种全选状态下的
coverage_info
。 - 初始剪枝: 如果在全选的情况下,仍然存在某个整数点覆盖数小于 2,那么无论如何删除线段,都不可能满足条件。此时答案直接为 0。
- 递归过程: 定义
dfs(k, coverage_info)
,表示我们正在考虑是否要删除第条线段,
coverage_info
是基于保留条线段(以及
也暂时保留)的覆盖信息。
- 决策1:保留第
条线段 我们不对
coverage_info
做任何改动,直接递归调用dfs(k+1, coverage_info)
,将返回的结果计入总方案数。 - 决策2:删除第
条线段 a. 我们从当前的
coverage_info
中“减去”第条线段
的贡献,得到一个新的
new_coverage_info
。 b. 合法性检查 (剪枝):检查这个new_coverage_info
是否依然满足“所有整数点覆盖数至少为 2”的条件。 c. 如果检查通过,说明删除第条线段是可行的,我们递归调用
dfs(k+1, new_coverage_info)
,将返回的结果也计入总方案数。 d. 如果检查不通过,说明删除第条线段后导致了不满足题意的覆盖情况,那么基于这个决策的所有后续方案都是无效的。我们剪掉这个分支,不进行递归。
- 决策1:保留第
- 递归出口: 当
k == n
时,说明我们已经对所有条线段都做出了“保留”或“删除”的决策,并且每一步都满足覆盖条件。这对应一种合法的方案,返回 1。
最终,dfs(0, initial_coverage_info)
的返回值就是题目的答案。
变量名说明: 题目描述中使用 代表线段数量,
代表坐标范围。代码中,输入时
m
对应线段数量, 对应坐标范围。为了忠于原始代码逻辑,我们的实现也遵循代码的变量命名。
代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
// 题目描述中的 n, m 在代码中分别用 m, n 表示
int max_coord, num_segments;
vector<pair<int, int>> edges;
long long MOD = 1e9 + 7;
// 严格按照Python代码的逻辑进行有效性检查
bool is_valid(const vector<vector<long long>>& n_r) {
if (n_r.size() <= 2) { // 至少需要有起点和终点
for (const auto& row : n_r) {
if (row[1] < 2) return false;
}
return true;
}
for (size_t i = 1; i < n_r.size() - 1; ++i) {
if (((n_r[i][1] + n_r[i][3]) < 2) ||
(n_r[i][1] < 2 && (n_r[i + 1][0] - n_r[i][0]) > 2)) {
return false;
}
}
if (n_r.back()[3] < 2) {
return false;
}
return true;
}
long long dfs(int seg_idx, vector<vector<long long>> n_r) {
if (seg_idx >= num_segments) {
return 1;
}
// 方案1: 保留第 seg_idx 条线段
long long count = dfs(seg_idx + 1, n_r);
// 方案2: 尝试删除第 seg_idx 条线段
pair<int, int> edge_to_remove = edges[seg_idx];
// 严格按照Python代码逻辑更新 n_r
for (size_t i = 1; i < n_r.size(); ++i) {
if (n_r[i][0] == edge_to_remove.first) {
n_r[i][2]--;
}
if (n_r[i][0] == edge_to_remove.second) {
n_r[i][3]--;
}
if (n_r[i][0] >= edge_to_remove.first && n_r[i][0] < edge_to_remove.second) {
n_r[i][1]--;
}
}
if (is_valid(n_r)) {
count = (count + dfs(seg_idx + 1, n_r)) % MOD;
}
return count;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> max_coord >> num_segments;
edges.resize(num_segments);
struct Boundary {
int coord;
int type; // 0 for start, 1 for end
};
vector<Boundary> boundaries;
for (int i = 0; i < num_segments; ++i) {
cin >> edges[i].first >> edges[i].second;
boundaries.push_back({edges[i].first, 0});
boundaries.push_back({edges[i].second, 1});
}
sort(boundaries.begin(), boundaries.end(), [](const Boundary& a, const Boundary& b) {
if (a.coord != b.coord) return a.coord < b.coord;
return a.type < b.type;
});
vector<vector<long long>> initial_coverage;
initial_coverage.push_back({0, 0, 0, 0});
// 严格按照Python代码的逻辑构建 initial_coverage
size_t cov_idx = 0;
for(const auto& b : boundaries) {
if (b.type == 0) { // start
if (b.coord == initial_coverage[cov_idx][0]) {
initial_coverage[cov_idx][1]++;
initial_coverage[cov_idx][2]++;
} else {
initial_coverage.push_back({(long long)b.coord, initial_coverage[cov_idx][1] + 1, 1, 0});
cov_idx++;
}
} else { // end
if (b.coord == initial_coverage[cov_idx][0]) {
initial_coverage[cov_idx][1]--;
initial_coverage[cov_idx][3]++;
} else {
initial_coverage.push_back({(long long)b.coord, initial_coverage[cov_idx][1] - 1, 0, 1});
cov_idx++;
}
}
}
if (!is_valid(initial_coverage)) {
cout << 0 << endl;
return 0;
}
cout << dfs(0, initial_coverage) << endl;
return 0;
}
import java.util.*;
public class Main {
static int maxCoord, numSegments;
static int[][] edges;
static final long MOD = 1_000_000_007L;
// 严格按照Python代码的逻辑进行有效性检查
static boolean isValid(List<long[]> n_r) {
if (n_r.size() <= 2) {
for (long[] row : n_r) {
if (row[1] < 2) return false;
}
return true;
}
for (int i = 1; i < n_r.size() - 1; i++) {
if (((n_r.get(i)[1] + n_r.get(i)[3]) < 2) ||
(n_r.get(i)[1] < 2 && (n_r.get(i + 1)[0] - n_r.get(i)[0]) > 2)) {
return false;
}
}
if (n_r.get(n_r.size() - 1)[3] < 2) {
return false;
}
return true;
}
static long dfs(int segIdx, List<long[]> n_r) {
if (segIdx >= numSegments) {
return 1;
}
// 方案1: 保留
List<long[]> keep_n_r = new ArrayList<>();
for (long[] row : n_r) {
keep_n_r.add(row.clone());
}
long count = dfs(segIdx + 1, keep_n_r);
// 方案2: 删除
int[] edgeToRemove = edges[segIdx];
for (int i = 1; i < n_r.size(); i++) {
long[] info = n_r.get(i);
if (info[0] == edgeToRemove[0]) {
info[2]--;
}
if (info[0] == edgeToRemove[1]) {
info[3]--;
}
if (info[0] >= edgeToRemove[0] && info[0] < edgeToRemove[1]) {
info[1]--;
}
}
if (isValid(n_r)) {
count = (count + dfs(segIdx + 1, n_r)) % MOD;
}
return count;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
maxCoord = sc.nextInt();
numSegments = sc.nextInt();
edges = new int[numSegments][2];
List<int[]> boundaries = new ArrayList<>();
for (int i = 0; i < numSegments; i++) {
edges[i][0] = sc.nextInt();
edges[i][1] = sc.nextInt();
boundaries.add(new int[]{edges[i][0], 0}); // 0: start
boundaries.add(new int[]{edges[i][1], 1}); // 1: end
}
boundaries.sort((a, b) -> {
if (a[0] != b[0]) return Integer.compare(a[0], b[0]);
return Integer.compare(a[1], b[1]);
});
List<long[]> initialCoverage = new ArrayList<>();
initialCoverage.add(new long[]{0, 0, 0, 0});
int covIdx = 0;
for (int[] b : boundaries) {
if (b[1] == 0) { // start
if (b[0] == initialCoverage.get(covIdx)[0]) {
initialCoverage.get(covIdx)[1]++;
initialCoverage.get(covIdx)[2]++;
} else {
initialCoverage.add(new long[]{b[0], initialCoverage.get(covIdx)[1] + 1, 1, 0});
covIdx++;
}
} else { // end
if (b[0] == initialCoverage.get(covIdx)[0]) {
initialCoverage.get(covIdx)[1]--;
initialCoverage.get(covIdx)[3]++;
} else {
initialCoverage.add(new long[]{b[0], initialCoverage.get(covIdx)[1] - 1, 0, 1});
covIdx++;
}
}
}
if (!isValid(initialCoverage)) {
System.out.println(0);
return;
}
System.out.println(dfs(0, initialCoverage));
}
}
import sys
# 增加递归深度限制
sys.setrecursionlimit(2000)
# n_coord: 坐标范围, m_seg: 线段数量
n_coord, m_seg = map(int, sys.stdin.readline().split())
edges = [list(map(int, sys.stdin.readline().split())) for _ in range(m_seg)]
MOD = 10**9 + 7
# 1. 扫描线预处理
boundary = []
for i, e in enumerate(edges):
boundary.append([e[0], 0, i]) # 0 表示起点
boundary.append([e[1], 1, i]) # 1 表示终点
boundary.sort()
# num 数组: [坐标, 该点覆盖数, 该点起点数, 该点终点数]
# 严格按照用户提供的原始逻辑构建
num = [[0, 0, 0, 0]]
i = 0
for b in boundary:
coord, type, _ = b
if type == 0: # start
if coord == num[i][0]:
num[i][1] += 1
num[i][2] += 1
else:
num.append([coord, num[i][1] + 1, 1, 0])
i += 1
else: # end
if coord == num[i][0]:
num[i][1] -= 1
num[i][3] += 1
else:
num.append([coord, num[i][1] - 1, 0, 1])
i += 1
# 2. 回溯搜索 (DFS)
# 严格按照用户提供的原始逻辑进行检查
def is_valid(n_r):
if len(n_r) <= 2:
for row in n_r:
if row[1] < 2: return False
return True
for i in range(1, len(n_r) - 1):
if ((n_r[i][1] + n_r[i][3]) < 2) or \
(n_r[i][1] < 2 and (n_r[i + 1][0] - n_r[i][0]) > 2):
return False
if n_r[-1][3] < 2:
return False
return True
memo = {}
def dfs(ind, n_r_tuple):
if ind >= m_seg:
return 1
state = (ind, n_r_tuple)
if state in memo:
return memo[state]
n_r = [list(row) for row in n_r_tuple]
# 决策1: 保留
res = dfs(ind + 1, tuple(map(tuple, n_r)))
# 决策2: 删除
e = edges[ind]
# 严格按照用户提供的原始逻辑更新
for i in range(1, len(n_r)):
if n_r[i][0] == e[0]:
n_r[i][2] -= 1
if n_r[i][0] == e[1]:
n_r[i][3] -= 1
if n_r[i][0] >= e[0] and n_r[i][0] < e[1]:
n_r[i][1] -= 1
if is_valid(n_r):
res = (res + dfs(ind + 1, tuple(map(tuple, n_r)))) % MOD
memo[state] = res
return res
# 3. 初始状态检查与调用
if not is_valid(num):
print(0)
else:
initial_num_tuple = tuple(map(tuple, num))
print(dfs(0, initial_num_tuple))
算法及复杂度
- 算法:扫描线 + 回溯搜索 (DFS) + 记忆化
- 时间复杂度:预处理阶段,对
个端点进行排序,时间复杂度为
(其中
是线段数)。回溯搜索阶段,由于状态由
(当前线段索引, 覆盖信息数组)
决定,状态空间巨大。但有效状态(满足覆盖条件)的数量可能有限,通过记忆化搜索可以避免重复计算。最坏情况下,时间复杂度仍接近,但在实际数据中表现会更好。
- 空间复杂度:扫描线和覆盖信息数组需要
的空间。记忆化哈希表和DFS的递归深度可能导致空间复杂度较高,最坏可达
。