Down Below

发布时间 2023-10-20 09:07:16作者: 小篪篪

因为看到题解区全是二分的 \(nm\log V\) 做法, 感觉不太优秀,所以就写了这篇题解。

观察题目,很容易联想到最短路,走到某个点的 \(val\) 就相当于路径长度,而由于有 \(a_i\) 的限制,所以我们要尽量让 \(val\) 大,也就相当于要求最长路径,而 \(b_i\) 就相当于是路径长度。

先考虑转化问题,最优问题通过二分转化成可行性问题。接着按照刚才的思路,运用类似于最短路的方法去处理询问。这里涉及到一个选择算法的问题,但由于求的是最长路径,首先排除 dijkstra ,接着排除肯定超时的 Floyd ,那结果显而易见是只能是 SPFA 。具体为什么不是 Bellman–Ford 我们后面会说明。

但我们进行最短路的时候会发现,\(b_i\) 只能算一次的性质让我们这个算法显得没那么正确了, 因为如果我们走了一个环, 那么我们就可以一直循环,再走出去。我们再回到题目,环其实是一个很特殊的东西,如果从 \(1\) 能走到一个环,那么我们可以把这个环及其到 \(1\) 的路径一起合并成一个新的 \(1\) ,原因就在于本来我们不能走回头路,但是有了环以后我们就可以利用环掉头。又因为每个点的度都是大于 \(2\) 的,那么每个点就一定至少在一个环上或者在一条环到 \(1\) 的路径上, 否则一定存在度数为 \(1\) 的点。所以我们的目标就从求最长路变成了求正环,这个问题似乎正是 SPFA 擅长的。

当我们开始用 SPFA 去求解正环的时候却发现,并不是每个正环都是可行的,有时候我们会因为重复跑环而跑到一些本来到不了的点,从而合并到了本来到不了的环,并且 SPFA 跑环的时间也并不正确,每次时间复杂度 \(O(nk)\) ,最多跑 \(m\) 次。我们需要发现些性质才能解决这个问题。由于我们一条边可以走的条件是 \(val > a_i\) ,那么我们考虑如果一个点在 SPFA 的过程中被更新了两次,那么设这个点为 \(u\) ,两次尝试更新它的点分别是 \(v, w\) (假设这个点是第一个被尝试更新两次的点,那么不可能从同一个人尝试更新两次, 否则更新它的那个点一定被更新过至少两次),假设 \(val_{w} \ge val_{v}\) ,那么有 \(val_{u} = b_{u} + val_{w} > val_{w} \ge val_{v} > a_{v}\)\(val_{u} > a_{v}\) ,所以 \(u\) 可以从 \(w\) 走过来并且再从 \(v\) 走回点 \(1\) , 所以 \(u\) 在一个环上, 那么我们直接记录从 \(1\) 走到 \(v, w\) 的路径即可, 由于这是第一个环,所以 \(v,w\) 都有唯一的从 \(1\) 来的路径。

因为我们每次合并至少会减少一个点,那么我们可以在每次合并后重新做 SPFA ,就可以了。这里解释一下刚才没有说的问题, 为什么要用 SPFA 而不用 Bellman–Ford 。可以发现在 SPFA 中一个点如果第二次进入队列那么就一定至少被尝试更新两次,那么就已经找到环了。这样来看的话 SPFA 的总时间应该是 \(m\) 的,而 Bellman–Ford 每次会用 \(m\) 的时间进行一个点的更新,那么对于类似于树的图的时候,他找到环的时间复杂度会退化成 \(nm\)

至此我们已经得到了一个 \(\mathrm{O}(nm\log V)\) 的能过的算法,而且这个算法如果打的漂亮也可以很快,但是很明显我们的目标并不是这样的一个算法,因为我们可以发现似乎二分的过程并不是必须的,我们明明知道要扩展更多需要使答案增加多少,我们二分却完全没有用到这个性质。

考虑我们最开始假如 \(1\)\(val\)\(0\) 那么我们一定无法继续进行寻找最长路径和缩环,那么假设 \(u\) 是一个我们能到达的点,\(v\) 为一个 \(u\) 的邻点且满足 \(v\) 不能到达,则我们要能使更多的点被到达至少要将答案增加的值就是 \(\min\{a_v - val_u\}\) 。所以只要我们没能将所有点都合成一个点,我们就尝试缩环或者无法缩环的时候选择增加答案,然后再重新做 SPFA ,那么缩点只会缩 \(n\) 次,更新答案只会因为一条边更新一次,总共就只会最多更新 \(m\) 次。所以最终也是有了一个 \(\mathrm{O}(m^2)\) 的算法了,虽然因为每次更新其实增加的能扩展的边可以直接加入队列中,所以可以优化到 \(\mathrm{O}(nm)\) ,但是因为懒就没有打了( \(\mathrm{O}(m^2)\) 已经可以将本来跑 200ms 多的快到最劣解的算法优化成 2023.10.19 之前的最优解我觉得已经足够了,而且再优化因为常数的原因可能时间并不会有太大差别)。

code:

#include <cstdio>
#include <algorithm>
#include <stack>
#include <vector>
using namespace std;

#define MAXN 1000
#define MAXM 2000
#define INF 0x3f3f3f3f

vector<pair<int, int> > s[MAXN + 5];
long long dp[MAXN + 5];
long long vis[MAXN + 5];
pair<int, int> sed[MAXM + 5];
int Fa[MAXN + 5];
long long sa[MAXN + 5], sb[MAXN + 5];

int main () {
	int t;
	
	scanf ("%d", &t);
	while (t --) {
		int n, m;
		
		scanf ("%d %d", &n, &m);
		for (int i = 2; i <= n; i ++) {
			scanf ("%lld", &sa[i]);
		}
		for (int i = 2; i <= n; i ++) {
			scanf ("%lld", &sb[i]);
		}
		for (int i = 1; i <= n; i ++) {
			s[i].clear();
		}
		for (int i = 1; i <= m; i ++) {
			scanf ("%d %d", &sed[i].first, &sed[i].second);
		}
		
		long long ans = 0;
		int num = n - 1;
		
		for (int i = 1; i <= n; i ++) {
			vis[i] = 0;
		}
		while (num) {
			long long smin = INF;
			int Last = n;
			
			while (num && num != Last){
				Last = num;
				for (int i = 1; i <= n; i ++) {
					dp[i] = 0;
					s[i].clear();
					Fa[i] = 0;
				}
				dp[1] = ans;
				for (int i = 1; i <= m; i ++) {
					int u = sed[i].first, v = sed[i].second;
					
					if (vis[u]) {
						u = 1;
					}
					if (vis[v]) {
						v = 1;
					}
					if (u != v) {
						s[u].push_back (make_pair(v, i));
						s[v].push_back (make_pair (u, i));
					}
				}
				for (int i = 2; i <= n; i ++) {
					if (vis[i]) {
						dp[1] += sb[i];
					}
				}
				
				stack<pair<int, int> > S;
				
				S.push(make_pair (1, 0));
				vis[1] = 1;
				while (!S.empty()) {
					pair<int, int> p = S.top();
					
					S.pop();
					for (auto i = s[p.first].begin(); i != s[p.first].end(); i ++) {
						if (i->second == p.second) {
							continue;
						}
						else if (Fa[i->first] || i->first == 1) {
							for (int j = i->first; j != 1; j = Fa[j]) {
								if (!vis[j]) {
									vis[j] = 1;
									num --;
								}
								else {
									break;
								}
							}
							for (int j = p.first; j != 1; j = Fa[j]) {
								if (!vis[j]) {
									vis[j] = 1;
									num --;
								}
								else {
									break;
								}
							}
						}
						else if (dp[p.first] > sa[i->first]) {
							dp[i->first] = sb[i->first] + dp[p.first];
							Fa[i->first] = p.first;
							S.push(*i);
						}
						else {
							smin = min (smin, sa[i->first] + 1 - dp[p.first]);
						}
					}
				}
			}
			if (num) {
				ans += smin;
			}
		}
		printf ("%lld\n", ans);
	}
}