首页 > 其他分享 >Down Below

Down Below

时间:2023-10-20 09:11:48浏览次数:34  
标签:val int SPFA Down Below MAXN 我们 first

因为看到题解区全是二分的 \(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);
	}
} 

标签:val,int,SPFA,Down,Below,MAXN,我们,first
From: https://www.cnblogs.com/flower-dream/p/17775855.html

相关文章

  • markdown基本使用语法(适合做笔记)
    markdown基础语法编辑器推荐vscode支持大量的插件,包括makrdown语法展示效果的插件。当安装这个插件之后,能够将文档和显示效果分成两个页面,就可以一边编辑代码,一边查看显示效果了,更大的优点是,纯文本状态下,无需考虑显示效果,加载速度更高,如果使用typora的话,当笔记达到两万字左右就......
  • 如何使用markdown语法展示纯文本效果,不考虑特殊字符带来的英雄
    作者希望能够像xml中的![CDATA[纯文本内容]]那样,里面包裹的内容就是纯文本的,因为有时候我不想因为一些特殊字符比如:#这种字符导致文字变大加粗网上查找了资料,不知道是这方面的内容少还是我输入的关键字有误,查到的资料寥寥无几,有效的是让你使用\(反斜杠)来转义内容,还有就是使用......
  • Go - Setting Up and Tearing Down Before and After Tests
    Problem: Youwanttosetupdataandanenvironmentfortestingandtearitdownafterthetestisrun.Solution: YoucancreatehelperfunctionsorusetheTestMainfeaturetocontroltheflowofthetestfunctions. Testingoftenneedsdataandanenv......
  • Go 提取字符串中url,转换为markdown格式并替换
     Go提取字符串中url,转换为markdown格式并替换//MakeContentUrlToMarkDown将字符串中url非markdown格式转[](url)格式funcMakeContentUrlToMarkDown(sourceStringstring)(resultStringstring){//urlReMustCompile:=regexp.MustCompile(".*(?P<URL>(http|https|......
  • CSDN、掘金、简书博客文章如何转为Markdown?
    1.在CSDN博文页面点击右键,选择“检查”(Google浏览器为例)。 2.在查看器中搜索“article_content”,找到对应内容,点击…复制为outerHTML。  3.打开网址https://tool.lu/markdown/,点击HTML2MD,粘贴html代码,转换成Markdown。 4.大功告成,同理操作掘金、简书或其他平台上博......
  • Linux 本地部署私有Stackedit Markdown编辑器远程访问
    StackEdit是一个受欢迎的Markdown编辑器,在GitHub上拥有20.7kStar!,它支持将Markdown笔记保存到多个仓库,包括Gitee、GitHub和Gitea。此在线笔记工具还提供了一些便捷功能,如拖拽或粘贴上传图片、文件搜索功能,以及可切换为炫酷的暗黑主题,这些功能特别适合那些喜欢使用Markdown来记录......
  • 学习MKdown
    Mkdown标题二级标题三级标题标题:#+标题名字,每多一个标题+一个#字体Hello,World!Hello,World!Hello,World!Hello,World!引用选择java,学习java.分割线“---”“***”图片超链接点击跳转到我的博客列表列表1列表2列表3 ---表格......
  • 在vscode中将markdown文件导出为pdf
    首先安装插件:然后在md文件中右键,选择如图所示选项跳转到预览界面之后,右键依次选择如图所示选项导出完成,pdf文件与md文件在同一文件目录下。......
  • markdown学习
    markdown学习标题#+空格,写完回车,最多六个字体HelloWord!粗体两边+两个*HelloWord!斜体两边+一个*HelloWord!斜体加粗两边三个*HelloWord!两边+两个~引用选择狂神说Java,走向人生巅峰。大于符号+空格分割线三个---或***图片用英文!+[图片名字]+(图片路径)超链......
  • [Ubuntu 20.04] 修复‘systemd-shutdown[1]: waiting for process: crond’需等待1分
    由于在2020-2021年期间下载过Linux版本的FreeDownloadManager(简称FDM,一款免费但不开源的跨平台下载工具),而该软件的官网被挂了木马,因此在此期间下载安装过FDM的Linux用户,其定时任务crond中都被挂上了木马。具体现象为,关机时需要等待1分30秒,系统显示‘systemd-shutdown[1]:waiti......