【洛谷】种树:为什么能用最长路来求最小值?

255 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述

详见 【洛谷】种树——区间与贪心的不解之缘 - 掘金 (juejin.cn)

昨天我们使用贪心问题求解了这道题,不过其实这道题有个更有意思的解法。

题目思路--除了贪心...

观察题意,我们其实可以把题目转化为一系列的不等式组,然后求在满足不等式组的条件下某一变量的最值。

首先,设 fif_i 为区域 11ii 之间种的树的数量。那么根据题目,我们可以列出以下的方程组:(注意是包括b和e的)

fe1fb11t1fe2fb21t2fe3fb31t3...fehfbh1thf_{e_1}-f_{b_1-1}\ge t_1\\ f_{e_2}-f_{b_2-1}\ge t_2\\ f_{e_3}-f_{b_3-1}\ge t_3\\ ...\\ f_{e_h}-f_{b_h-1}\ge t_h

别忘了还有条件“每个部分为一个单位尺寸大小并最多可种一棵树。”,另外,有个暗含条件是每个单位尺寸的树的数量不为负数。

0f1f010f2f110f3f21...0fnfn110\le f_{1}-f_{0}\le 1\\ 0\le f_{2}-f_{1}\le 1\\ 0\le f_{3}-f_{2}\le 1\\ ...\\ 0\le f_{n}-f_{n-1}\le 1

最后我们要求的是 f_n 的最小值。怎么求呢?

又见差分约束系统!

【学海拾遗】不等式组求解——图论的巧妙使用 - 掘金 (juejin.cn)

根据这篇文章,我们得知了不等式组的求解可以转化为图论的最短路问题。

但是这里有点小问题:

  • 首先这个题要求的是一个特定的解,其中fnf_n要求最小。

对于这种求最大或者最小的特定解的问题,我们也有通用解法,那就是 寻找一个点,它的distdist肯定为0,就以这个点为起点,求目标结点的最短路即可~

在这里我们找到的点就是f0f_0对于的点,因为f0=0f_0=0

  • 其次,这里的等号都是小于号,如果转化为大于号,就会出现一堆边权为 t1,t2,t3-t_1,-t_2,-t_3的边。

出现负权边倒是没问题,但是我们惊讶的发现这样建图,是不能从00到达nn点的,会得出无解!

因此我们换个思路:求最长路即可。

固定一个结点 ii, 设 dist[j]dist[j] 为图中结点 ii 到结点 jj最长路径。 那么当 jjkk 之间有一条有向边,边权为ww 时,有以下性质:

dist[j]+wdist[k]dist[k]dist[j]wdist[j]+w\le dist[k]\\ 即 dist[k]-dist[j]\ge w

然后上面的不等式组就可以转换成如下的图:

图有n+1n+1个结点,由于 f0f_0 必为 00,那我们固定 dist[0]=0dist[0]=0, 即图中的最长路径是指从00出发的最长路径。

对于 i[1,h]i\in[1,h],从bi1b_i-1eie_i 建一条权值为 tit_i 的边;

对于 i[1,n]i\in[1,n],从 iii1i-1 建一条权值为 1-1 的边,从 i1i-1ii 建一条权值为 00 的边;

最后使用SPFA求最长路即可。

为什么是最长路?

可能有人会疑惑,为什么题目 求的是 fnf_n的最小值,但我们却求得是图的最长路径??? 为什么不求最短路径呢?

大家可以仔细想想。当我们根据那些不等式建图的时候,其实我们已经定下了 dist[i]dist[i] 的含义,那就是最长路径, 而不是最短路径!如果我们用最短路径去求,很显然得出来的一系列 dist[i]dist[i] 是不满足上述的不等式组的。

而我们求出了最长路径时,就意味着我们得到了一个等于号, 也就是说存在一个结点 ii ,有 dist[i]+w=dist[n]dist[i]+w=dist[n],这意味着什么?意味着我们求出来了不等式组的一个临界解。而临界解通常意味着最值,并且无论是最大值还是最小值,每个情景绝对只有一个最值。 在这里是最小值,因此我们就可以把它当作结果了。总而言之,建模时求图的“最长“路径还是”最短”路径和解题时求的“最大“值“最小“值没有关系,我们只需要确定我们得到了”最“,我们就可以把它作为答案。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cctype>
#include<string>
#include<cmath>
#include<cstring>
#include<queue>
#include<numeric>
#define fru(a,b,c) for(int a=b;a<=c;a++)
#define frd(a,b,c) for(int a=b;a>=c;a--)
#define fr(a,b) for(int a=0;a<b;a++)
#define pb push_back
#define mp make_pair
#define sof sizeof
using namespace std;
using ll=long long;
using db=double;
ll rd() {
        ll k = 0, f = 1;
        char c = getchar();
        while (c < '0' || c>'9') {
            if (c == '-')f = -1;
            c = getchar();
        }
        while (c >= '0' && c <= '9') {
            k = (k << 1) + (k << 3) + (c ^ 48);
            c = getchar();
        }
        return f > 0 ? k : -k;
    }
const db eps=1e-6;
const int inf=0x3ffffff3;
const ll linf=0x3ffffffffffffff3;
const db dinf=1e10;
const double pi=acos(-1);
inline bool is0(db a){return a < eps && a > -eps;}
const int maxn=100000+5;
struct edge{
	int v,w;
};
vector<edge> graph[maxn];
bool fd[maxn];
int dist[maxn];
int timee[maxn];
bool SPFA(int n){
	queue<int> q;
	fill_n(dist,n+1,-inf);
	dist[0]=0;
	fd[0]=1;
	q.push(0);
	while(!q.empty()){
		int u=q.front();q.pop();
		fd[u]=0;
		for(auto e:graph[u]){
			int v=e.v,w=e.w;
			if(dist[u]+e.w>dist[v]){
				dist[v]=dist[u]+e.w;
				if(!fd[v]){
					q.push(v);
					fd[v]=1;
					timee[v]++;
					if(timee[v]>n)return 0;
				}
			}
		}
	}
	return 1;
}
int main(){
	int n=rd(),h=rd();
	fr(i,h){
		int b=rd(),e=rd(),t=rd();
		graph[b-1].pb({e,t});
	}
	fru(i,1,n){
		graph[i-1].pb({i,0});
		graph[i].pb({i-1,-1});
	}
	SPFA(n);
	cout<<dist[n];
	
}