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);
	}
}