P10934 西瓜種植 解題報告

Brilliant11001發表於2024-09-01

題目傳送門

這道題也可以用貪心來做,這裡講一下差分約束的做法。

看到題中給出了 \(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;
} 

相關文章