題目傳送門
這道題也可以用貪心來做,這裡講一下差分約束的做法。
看到題中給出了 \(m\) 條限制性的語句就聯想到差分約束(差分約束的題還是很顯眼的)。
做差分約束的題首先得把題面抽象成很多個不等式,所以我們先來轉化一下題意。
首先發現求最小值,那麼先確定轉化方向:將所有條件轉換成大於或大於等於,然後建邊跑最長路。
設 \(x_i\) 表示前 \(i\) 塊地共種了多少個西瓜。
那麼對於每個關係可以理解為 \(sum_b - sum_{a - 1} \ge c\),就是說 \([a, b]\) 這塊地種的西瓜數量要大於等於 \(c\),那麼就從 \(a - 1\) 向 \(b\) 連一條長度為 \(c\) 的有向邊。
但是光這樣建邊你會發現根本做不了一點,因為你根本不知道該從哪裡開始跑最長路,而且無法正確求解。
這就是這道題非常有意思的一點,因為除了給出的資料需要建邊,還有隱藏的建邊關係。
注意:
每處最多隻能種一個西瓜。
這其實隱藏了一個關係:\(\forall i \in [1,n],0\le x_i - x_{i - 1} \le 1\)。
拆開來看就是:
\[\begin{cases}
x_i - x_{i - 1}\ge 0\\
x_{i - 1} - x_i\ge -1
\end{cases}
\]
所以,要從 \(i - 1\) 向 \(i\) 連一條長度為 \(0\) 的有向邊,
從 \(i\) 向 \(i - 1\) 連一條長度為 \(-1\) 的有向邊。
綜上,再根據剛剛的建圖方式,從 \(0\) 號點跑最長路,答案就是 \(x_n\)。
\(\texttt{Code:}\)
#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 30010, M = 100010;
int n, m, C;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
bool vis[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int ans;
void spfa(int s) {
queue<int> q;
q.push(s);
memset(dist, -0x3f, sizeof dist);
dist[s] = 0;
vis[s] = true;
while(q.size()) {
int t = q.front();
q.pop();
vis[t] = false;
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if(dist[j] < dist[t] + w[i]) {
dist[j] = dist[t] + w[i];
if(!vis[j]) {
vis[j] = true;
q.push(j);
}
}
}
}
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
add(i - 1, i, 0);
add(i, i - 1, -1);
}
for(int i = 1, a, b, c; i <= m; i++) {
scanf("%d%d%d", &a, &b, &c);
add(a - 1, b, c);
}
spfa(0);
printf("%d", dist[n]);
return 0;
}